diff options
author | Andrew Turner <andrew@FreeBSD.org> | 2013-01-18 20:06:45 +0000 |
---|---|---|
committer | Andrew Turner <andrew@FreeBSD.org> | 2013-01-18 20:06:45 +0000 |
commit | 58aabf08b77d221489f10e274812ec60917c21a8 (patch) | |
tree | b946f82269be87d83f086167c762c362e734c5bb | |
parent | 37dfff057418e02f8e5322da12684dd927e3d881 (diff) |
Import compiler-rt r172839.vendor/compiler-rt/compiler-rt-r172839
Notes
Notes:
svn path=/vendor/compiler-rt/dist/; revision=245614
svn path=/vendor/compiler-rt/compiler-rt-r172839/; revision=245615; tag=vendor/compiler-rt/compiler-rt-r172839
437 files changed, 25435 insertions, 6095 deletions
diff --git a/.arcconfig b/.arcconfig new file mode 100644 index 000000000000..413b70b05f7b --- /dev/null +++ b/.arcconfig @@ -0,0 +1,4 @@ +{ + "project_id" : "compiler-rt", + "conduit_uri" : "http://llvm-reviews.chandlerc.com/" +} diff --git a/CMakeLists.txt b/CMakeLists.txt index 97835a1e945e..04d6e9763bf8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,49 +15,157 @@ include(LLVMParseArguments) # runtime libraries. cmake_minimum_required(VERSION 2.8.8) -# FIXME: Below we assume that the target build of LLVM/Clang is x86, which is -# not at all valid. Much of this can be fixed just by switching to use -# a just-built-clang binary for the compiles. +# Add path for custom modules +set(CMAKE_MODULE_PATH + ${CMAKE_MODULE_PATH} + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules" + ) +include(AddCompilerRT) + +set(COMPILER_RT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) # Detect whether the current target platform is 32-bit or 64-bit, and setup # the correct commandline flags needed to attempt to target 32-bit and 64-bit. -if(CMAKE_SIZEOF_VOID_P EQUAL 4) - set(TARGET_X86_64_CFLAGS "-m64") - set(TARGET_I386_CFLAGS "") +if(CMAKE_SIZEOF_VOID_P EQUAL 4 OR LLVM_BUILD_32_BITS) + set(TARGET_64_BIT_CFLAGS "-m64") + set(TARGET_32_BIT_CFLAGS "") else() if(NOT CMAKE_SIZEOF_VOID_P EQUAL 8) message(FATAL_ERROR "Please use a sane architecture with 4 or 8 byte pointers.") endif() - set(TARGET_X86_64_CFLAGS "") - set(TARGET_I386_CFLAGS "-m32") + set(TARGET_64_BIT_CFLAGS "") + set(TARGET_32_BIT_CFLAGS "-m32") endif() +# FIXME: Below we assume that the target build of LLVM/Clang is x86, which is +# not at all valid. Much of this can be fixed just by switching to use +# a just-built-clang binary for the compiles. + +set(TARGET_x86_64_CFLAGS ${TARGET_64_BIT_CFLAGS}) +set(TARGET_i386_CFLAGS ${TARGET_32_BIT_CFLAGS}) + +set(COMPILER_RT_SUPPORTED_ARCH + x86_64 i386) + +function(get_target_flags_for_arch arch out_var) + list(FIND COMPILER_RT_SUPPORTED_ARCH ${arch} ARCH_INDEX) + if(ARCH_INDEX EQUAL -1) + message(FATAL_ERROR "Unsupported architecture: ${arch}") + else() + set(${out_var} ${TARGET_${arch}_CFLAGS} PARENT_SCOPE) + endif() +endfunction() + # Try to compile a very simple source file to ensure we can target the given # platform. We use the results of these tests to build only the various target # runtime libraries supported by our current compilers cross-compiling # abilities. set(SIMPLE_SOURCE64 ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/simple64.c) file(WRITE ${SIMPLE_SOURCE64} "#include <stdlib.h>\nint main() {}") -try_compile(CAN_TARGET_X86_64 ${CMAKE_BINARY_DIR} ${SIMPLE_SOURCE64} - COMPILE_DEFINITIONS "${TARGET_X86_64_CFLAGS}" - CMAKE_FLAGS "-DCMAKE_EXE_LINKER_FLAGS:STRING=${TARGET_X86_64_CFLAGS}") +try_compile(CAN_TARGET_x86_64 ${CMAKE_BINARY_DIR} ${SIMPLE_SOURCE64} + COMPILE_DEFINITIONS "${TARGET_x86_64_CFLAGS}" + CMAKE_FLAGS "-DCMAKE_EXE_LINKER_FLAGS:STRING=${TARGET_x86_64_CFLAGS}") set(SIMPLE_SOURCE32 ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/simple32.c) file(WRITE ${SIMPLE_SOURCE32} "#include <stdlib.h>\nint main() {}") -try_compile(CAN_TARGET_I386 ${CMAKE_BINARY_DIR} ${SIMPLE_SOURCE32} - COMPILE_DEFINITIONS "${TARGET_I386_CFLAGS}" - CMAKE_FLAGS "-DCMAKE_EXE_LINKER_FLAGS:STRING=${TARGET_I386_CFLAGS}") - -# Because compiler-rt spends a lot of time setting up custom compile flags, -# define a handy helper function for it. The compile flags setting in CMake -# has serious issues that make its syntax challenging at best. -function(set_target_compile_flags target) - foreach(arg ${ARGN}) - set(argstring "${argstring} ${arg}") +try_compile(CAN_TARGET_i386 ${CMAKE_BINARY_DIR} ${SIMPLE_SOURCE32} + COMPILE_DEFINITIONS "${TARGET_i386_CFLAGS}" + CMAKE_FLAGS "-DCMAKE_EXE_LINKER_FLAGS:STRING=${TARGET_i386_CFLAGS}") + +# We only support running instrumented tests when we're not cross compiling +# and target a unix-like system. On Android we define the rules for building +# unit tests, but don't execute them. +if("${CMAKE_HOST_SYSTEM}" STREQUAL "${CMAKE_SYSTEM}" AND UNIX AND NOT ANDROID) + set(COMPILER_RT_CAN_EXECUTE_TESTS TRUE) +else() + set(COMPILER_RT_CAN_EXECUTE_TESTS FALSE) +endif() + +function(filter_available_targets out_var) + set(archs) + foreach(arch ${ARGN}) + list(FIND COMPILER_RT_SUPPORTED_ARCH ${arch} ARCH_INDEX) + if(NOT (ARCH_INDEX EQUAL -1) AND CAN_TARGET_${arch}) + list(APPEND archs ${arch}) + endif() endforeach() - set_property(TARGET ${target} PROPERTY COMPILE_FLAGS "${argstring}") + set(${out_var} ${archs} PARENT_SCOPE) endfunction() +# Provide some common commmandline flags for Sanitizer runtimes. +set(SANITIZER_COMMON_CFLAGS + -fPIC + -fno-builtin + -fno-exceptions + -fomit-frame-pointer + -funwind-tables + -O3 + ) +if(NOT WIN32) + list(APPEND SANITIZER_COMMON_CFLAGS -fvisibility=hidden) +endif() +# Build sanitizer runtimes with debug info. +check_cxx_compiler_flag(-gline-tables-only SUPPORTS_GLINE_TABLES_ONLY_FLAG) +if(SUPPORTS_GLINE_TABLES_ONLY_FLAG) + list(APPEND SANITIZER_COMMON_CFLAGS -gline-tables-only) +else() + list(APPEND SANITIZER_COMMON_CFLAGS -g) +endif() +# Warnings suppressions. +check_cxx_compiler_flag(-Wno-variadic-macros SUPPORTS_NO_VARIADIC_MACROS_FLAG) +if(SUPPORTS_NO_VARIADIC_MACROS_FLAG) + list(APPEND SANITIZER_COMMON_CFLAGS -Wno-variadic-macros) +endif() +check_cxx_compiler_flag(-Wno-c99-extensions SUPPORTS_NO_C99_EXTENSIONS_FLAG) +if(SUPPORTS_NO_C99_EXTENSIONS_FLAG) + list(APPEND SANITIZER_COMMON_CFLAGS -Wno-c99-extensions) +endif() +if(APPLE) + list(APPEND SANITIZER_COMMON_CFLAGS -mmacosx-version-min=10.5) +endif() + +# Architectures supported by Sanitizer runtimes. Specific sanitizers may +# support only subset of these (e.g. TSan works on x86_64 only). +filter_available_targets(SANITIZER_COMMON_SUPPORTED_ARCH + x86_64 i386) + +# Compute the Clang version from the LLVM version. +# FIXME: We should be able to reuse CLANG_VERSION variable calculated +# in Clang cmake files, instead of copying the rules here. +string(REGEX MATCH "[0-9]+\\.[0-9]+(\\.[0-9]+)?" CLANG_VERSION + ${PACKAGE_VERSION}) +# Setup the paths where compiler-rt runtimes and headers should be stored. +set(LIBCLANG_INSTALL_PATH lib${LLVM_LIBDIR_SUFFIX}/clang/${CLANG_VERSION}) +string(TOLOWER ${CMAKE_SYSTEM_NAME} LIBCLANG_OS_DIR) + +# Install compiler-rt headers. +install(DIRECTORY include/ + DESTINATION ${LIBCLANG_INSTALL_PATH}/include + FILES_MATCHING + PATTERN "*.h" + PATTERN ".svn" EXCLUDE + ) + +# Call add_clang_compiler_rt_libraries to make sure that targets are built +# and installed in the directories where Clang driver expects to find them. +macro(add_clang_compiler_rt_libraries) + # Setup output directories so that clang in build tree works. + set_target_properties(${ARGN} PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY + ${LLVM_BINARY_DIR}/lib/clang/${CLANG_VERSION}/lib/${LIBCLANG_OS_DIR} + LIBRARY_OUTPUT_DIRECTORY + ${LLVM_BINARY_DIR}/lib/clang/${CLANG_VERSION}/lib/${LIBCLANG_OS_DIR} + ) + # Add installation command. + install(TARGETS ${ARGN} + ARCHIVE DESTINATION ${LIBCLANG_INSTALL_PATH}/lib/${LIBCLANG_OS_DIR} + LIBRARY DESTINATION ${LIBCLANG_INSTALL_PATH}/lib/${LIBCLANG_OS_DIR} + ) +endmacro(add_clang_compiler_rt_libraries) + +# Add the public header's directory to the includes for all of compiler-rt. +include_directories(include) + add_subdirectory(lib) if(LLVM_INCLUDE_TESTS) diff --git a/LICENSE.TXT b/LICENSE.TXT index f7179425605e..6aab1f694cc2 100644 --- a/LICENSE.TXT +++ b/LICENSE.TXT @@ -14,7 +14,7 @@ Full text of the relevant licenses is included below. University of Illinois/NCSA Open Source License -Copyright (c) 2009-2012 by the contributors listed in CREDITS.TXT +Copyright (c) 2009-2013 by the contributors listed in CREDITS.TXT All rights reserved. @@ -55,7 +55,7 @@ SOFTWARE. ============================================================================== -Copyright (c) 2009-2012 by the contributors listed in CREDITS.TXT +Copyright (c) 2009-2013 by the contributors listed in CREDITS.TXT Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -92,7 +92,7 @@ test: %/.dir: $(Summary) " MKDIR: $*" $(Verb) $(MKDIR) $* > /dev/null - $(Verb) $(DATE) > $@ + $(Verb) echo 'Created.' > $@ # Remove directories %/.remove: @@ -117,7 +117,7 @@ $(call Set,Tmp.Configs,$($(Tmp.Key).Configs)) $(call Set,Tmp.ObjPath,$(ProjObjRoot)/$(Tmp.Name)) # Top-Level Platform Target -$(Tmp.Name):: $(Tmp.Configs:%=$(Tmp.ObjPath)/%/libcompiler_rt.a) +$(Tmp.Name):: $(Tmp.Configs:%=$(Tmp.Name)-%) .PHONY: $(Tmp.Name) clean:: @@ -131,6 +131,15 @@ endef define PerPlatformConfig_template $(call Set,Tmp.Config,$(1)) $(call Set,Tmp.ObjPath,$(ProjObjRoot)/$(Tmp.Name)/$(Tmp.Config)) +$(call Set,Tmp.SHARED_LIBRARY,$(strip \ + $(call GetCNAVar,SHARED_LIBRARY,$(Tmp.Key),$(Tmp.Config),$(Tmp.Arch)))) +$(call Set,Tmp.SHARED_LIBRARY_SUFFIX,$(strip \ + $(call GetCNAVar,SHARED_LIBRARY_SUFFIX,$(Tmp.Key),$(Tmp.Config),$(Tmp.Arch)))) + +# Compute the library suffix. +$(if $(call streq,1,$(Tmp.SHARED_LIBRARY)), + $(call Set,Tmp.LibrarySuffix,$(Tmp.SHARED_LIBRARY_SUFFIX)), + $(call Set,Tmp.LibrarySuffix,a)) # Compute the archs to build, depending on whether this is a universal build or # not. @@ -142,8 +151,8 @@ $(call Set,Tmp.ArchsToBuild,\ $(call VarOrDefault,$(Tmp.Key).Arch.$(Tmp.Config),$($(Tmp.Key).Arch)))) # Copy or lipo to create the per-config library. -$(call Set,Tmp.Inputs,$(Tmp.ArchsToBuild:%=$(Tmp.ObjPath)/%/libcompiler_rt.a)) -$(Tmp.ObjPath)/libcompiler_rt.a: $(Tmp.Inputs) $(Tmp.ObjPath)/.dir +$(call Set,Tmp.Inputs,$(Tmp.ArchsToBuild:%=$(Tmp.ObjPath)/%/libcompiler_rt.$(Tmp.LibrarySuffix))) +$(Tmp.ObjPath)/libcompiler_rt.$(Tmp.LibrarySuffix): $(Tmp.Inputs) $(Tmp.ObjPath)/.dir $(Summary) " FINAL-ARCHIVE: $(Tmp.Name)/$(Tmp.Config): $$@" -$(Verb) $(RM) $$@ $(if $(call streq,1,$(words $(Tmp.ArchsToBuild))), \ @@ -152,7 +161,7 @@ $(Tmp.ObjPath)/libcompiler_rt.a: $(Tmp.Inputs) $(Tmp.ObjPath)/.dir .PRECIOUS: $(Tmp.ObjPath)/.dir # Per-Config Targets -$(Tmp.Name)-$(Tmp.Config):: $(Tmp.ObjPath)/libcompiler_rt.a +$(Tmp.Name)-$(Tmp.Config):: $(Tmp.ObjPath)/libcompiler_rt.$(Tmp.LibrarySuffix) .PHONY: $(Tmp.Name)-$(Tmp.Config) # Per-Config-Arch Libraries @@ -172,10 +181,21 @@ $(call Set,Tmp.AR,$(strip \ $(call GetCNAVar,AR,$(Tmp.Key),$(Tmp.Config),$(Tmp.Arch)))) $(call Set,Tmp.ARFLAGS,$(strip \ $(call GetCNAVar,ARFLAGS,$(Tmp.Key),$(Tmp.Config),$(Tmp.Arch)))) +$(call Set,Tmp.CC,$(strip \ + $(call GetCNAVar,CC,$(Tmp.Key),$(Tmp.Config),$(Tmp.Arch)))) +$(call Set,Tmp.LDFLAGS,$(strip \ + $(call GetCNAVar,LDFLAGS,$(Tmp.Key),$(Tmp.Config),$(Tmp.Arch)))) $(call Set,Tmp.RANLIB,$(strip \ $(call GetCNAVar,RANLIB,$(Tmp.Key),$(Tmp.Config),$(Tmp.Arch)))) $(call Set,Tmp.RANLIBFLAGS,$(strip \ $(call GetCNAVar,RANLIBFLAGS,$(Tmp.Key),$(Tmp.Config),$(Tmp.Arch)))) +$(call Set,Tmp.SHARED_LIBRARY,$(strip \ + $(call GetCNAVar,SHARED_LIBRARY,$(Tmp.Key),$(Tmp.Config),$(Tmp.Arch)))) + +# Compute the library suffix. +$(if $(call streq,1,$(Tmp.SHARED_LIBRARY)), + $(call Set,Tmp.LibrarySuffix,$(Tmp.SHARED_LIBRARY_SUFFIX)), + $(call Set,Tmp.LibrarySuffix,a)) # Compute the object inputs for this library. $(call Set,Tmp.Inputs,\ @@ -188,10 +208,18 @@ $(Tmp.ObjPath)/libcompiler_rt.a: $(Tmp.Inputs) $(Tmp.ObjPath)/.dir -$(Verb) $(RM) $$@ $(Verb) $(Tmp.AR) $(Tmp.ARFLAGS) $$@ $(Tmp.Inputs) $(Verb) $(Tmp.RANLIB) $(Tmp.RANLIBFLAGS) $$@ +$(Tmp.ObjPath)/libcompiler_rt.dylib: $(Tmp.Inputs) $(Tmp.ObjPath)/.dir + $(Summary) " DYLIB: $(Tmp.Name)/$(Tmp.Config)/$(Tmp.Arch): $$@" + $(Verb) $(Tmp.CC) -arch $(Tmp.Arch) -dynamiclib -o $$@ \ + $(Tmp.Inputs) $(Tmp.LDFLAGS) +$(Tmp.ObjPath)/libcompiler_rt.so: $(Tmp.Inputs) $(Tmp.ObjPath)/.dir + $(Summary) " SO: $(Tmp.Name)/$(Tmp.Config)/$(Tmp.Arch): $$@" + $(Verb) $(Tmp.CC) -shared -o $$@ \ + $(Tmp.Inputs) $(Tmp.LDFLAGS) .PRECIOUS: $(Tmp.ObjPath)/.dir # Per-Config-Arch Targets -$(Tmp.Name)-$(Tmp.Config)-$(Tmp.Arch):: $(Tmp.ObjPath)/libcompiler_rt.a +$(Tmp.Name)-$(Tmp.Config)-$(Tmp.Arch):: $(Tmp.ObjPath)/libcompiler_rt.$(Tmp.LibrarySuffix) .PHONY: $(Tmp.Name)-$(Tmp.Config)-$(Tmp.Arch) # Per-Config-Arch-SubDir Objects diff --git a/SDKs/darwin/usr/include/stdio.h b/SDKs/darwin/usr/include/stdio.h index 3b560369f326..63b10a86b632 100644 --- a/SDKs/darwin/usr/include/stdio.h +++ b/SDKs/darwin/usr/include/stdio.h @@ -17,6 +17,10 @@ #ifndef __STDIO_H__ #define __STDIO_H__ +#if defined(__cplusplus) +extern "C" { +#endif + typedef struct __sFILE FILE; typedef __SIZE_TYPE__ size_t; @@ -51,11 +55,30 @@ typedef __SIZE_TYPE__ size_t; # define stderr __stderrp extern FILE *__stderrp; +#ifndef SEEK_SET +#define SEEK_SET 0 /* set file offset to offset */ +#endif +#ifndef SEEK_CUR +#define SEEK_CUR 1 /* set file offset to current plus offset */ +#endif +#ifndef SEEK_END +#define SEEK_END 2 /* set file offset to EOF plus offset */ +#endif + int fclose(FILE *); int fflush(FILE *); -FILE *fopen(const char * restrict, const char * restrict) __asm(__FOPEN_NAME); -int fprintf(FILE * restrict, const char * restrict, ...); -size_t fwrite(const void * restrict, size_t, size_t, FILE * restrict) +FILE *fopen(const char * __restrict, const char * __restrict) __asm(__FOPEN_NAME); +int fprintf(FILE * __restrict, const char * __restrict, ...); +size_t fwrite(const void * __restrict, size_t, size_t, FILE * __restrict) __asm(__FWRITE_NAME); +size_t fread(void * __restrict, size_t, size_t, FILE * __restrict); +long ftell(FILE *); +int fseek(FILE *, long, int); + +int snprintf(char * __restrict, size_t, const char * __restrict, ...); + +#if defined(__cplusplus) +} +#endif #endif /* __STDIO_H__ */ diff --git a/SDKs/linux/usr/include/stdio.h b/SDKs/linux/usr/include/stdio.h index ddfe75548793..7c258d2aca17 100644 --- a/SDKs/linux/usr/include/stdio.h +++ b/SDKs/linux/usr/include/stdio.h @@ -26,10 +26,17 @@ extern struct _IO_FILE *stdin; extern struct _IO_FILE *stdout; extern struct _IO_FILE *stderr; +#define SEEK_SET 0 /* set file offset to offset */ +#define SEEK_CUR 1 /* set file offset to current plus offset */ +#define SEEK_END 2 /* set file offset to EOF plus offset */ + extern int fclose(FILE *); extern int fflush(FILE *); extern FILE *fopen(const char * restrict, const char * restrict); extern int fprintf(FILE * restrict, const char * restrict, ...); extern size_t fwrite(const void * restrict, size_t, size_t, FILE * restrict); +extern size_t fread(void * restrict, size_t, size_t, FILE * restrict); +extern long ftell(FILE *); +extern int fseek(FILE *, long, int); #endif /* __STDIO_H__ */ diff --git a/cmake/Modules/AddCompilerRT.cmake b/cmake/Modules/AddCompilerRT.cmake new file mode 100644 index 000000000000..e90253fdfa1e --- /dev/null +++ b/cmake/Modules/AddCompilerRT.cmake @@ -0,0 +1,49 @@ +include(AddLLVM) +include(LLVMParseArguments) +include(CompilerRTUtils) + +# Tries to add "object library" target for a given architecture +# with name "<name>.<arch>" if architecture can be targeted. +# add_compiler_rt_object_library(<name> <arch> +# SOURCES <source files> +# CFLAGS <compile flags>) +macro(add_compiler_rt_object_library name arch) + if(CAN_TARGET_${arch}) + parse_arguments(LIB "SOURCES;CFLAGS" "" ${ARGN}) + add_library(${name}.${arch} OBJECT ${LIB_SOURCES}) + set_target_compile_flags(${name}.${arch} + ${TARGET_${arch}_CFLAGS} ${LIB_CFLAGS}) + else() + message(FATAL_ERROR "Archtecture ${arch} can't be targeted") + endif() +endmacro() + +# Unittests support. +set(COMPILER_RT_GTEST_PATH ${LLVM_MAIN_SRC_DIR}/utils/unittest/googletest) +set(COMPILER_RT_GTEST_SOURCE ${COMPILER_RT_GTEST_PATH}/gtest-all.cc) +set(COMPILER_RT_GTEST_INCLUDE_CFLAGS + -DGTEST_NO_LLVM_RAW_OSTREAM=1 + -I${COMPILER_RT_GTEST_PATH}/include +) + +# Use Clang to link objects into a single executable with just-built +# Clang, using specific link flags. Make executable a part of provided +# test_suite. +# add_compiler_rt_test(<test_suite> <test_name> +# OBJECTS <object files> +# DEPS <deps (e.g. runtime libs)> +# LINK_FLAGS <link flags>) +macro(add_compiler_rt_test test_suite test_name) + parse_arguments(TEST "OBJECTS;DEPS;LINK_FLAGS" "" ${ARGN}) + get_unittest_directory(OUTPUT_DIR) + file(MAKE_DIRECTORY ${OUTPUT_DIR}) + set(output_bin "${OUTPUT_DIR}/${test_name}") + add_custom_command( + OUTPUT ${output_bin} + COMMAND clang ${TEST_OBJECTS} -o "${output_bin}" + ${TEST_LINK_FLAGS} + DEPENDS clang ${TEST_DEPS}) + add_custom_target(${test_name} DEPENDS ${output_bin}) + # Make the test suite depend on the binary. + add_dependencies(${test_suite} ${test_name}) +endmacro() diff --git a/cmake/Modules/CompilerRTCompile.cmake b/cmake/Modules/CompilerRTCompile.cmake new file mode 100644 index 000000000000..2794cabe59c5 --- /dev/null +++ b/cmake/Modules/CompilerRTCompile.cmake @@ -0,0 +1,16 @@ +include(LLVMParseArguments) + +# Compile a source into an object file with just-built Clang using +# a provided compile flags and dependenices. +# clang_compile(<object> <source> +# CFLAGS <list of compile flags> +# DEPS <list of dependencies>) +macro(clang_compile object_file source) + parse_arguments(SOURCE "CFLAGS;DEPS" "" ${ARGN}) + get_filename_component(source_rpath ${source} REALPATH) + add_custom_command( + OUTPUT ${object_file} + COMMAND clang ${SOURCE_CFLAGS} -c -o "${object_file}" ${source_rpath} + MAIN_DEPENDENCY ${source} + DEPENDS clang ${SOURCE_DEPS}) +endmacro() diff --git a/cmake/Modules/CompilerRTLink.cmake b/cmake/Modules/CompilerRTLink.cmake new file mode 100644 index 000000000000..85030a725e19 --- /dev/null +++ b/cmake/Modules/CompilerRTLink.cmake @@ -0,0 +1,14 @@ +include(LLVMParseArguments) + +# Link a shared library with just-built Clang. +# clang_link_shared(<output.so> +# OBJECTS <list of input objects> +# LINKFLAGS <list of link flags> +# DEPS <list of dependencies>) +macro(clang_link_shared so_file) + parse_arguments(SOURCE "OBJECTS;LINKFLAGS;DEPS" "" ${ARGN}) + add_custom_command( + OUTPUT ${so_file} + COMMAND clang -o "${so_file}" -shared ${SOURCE_LINKFLAGS} ${SOURCE_OBJECTS} + DEPENDS clang ${SOURCE_DEPS}) +endmacro() diff --git a/cmake/Modules/CompilerRTUtils.cmake b/cmake/Modules/CompilerRTUtils.cmake new file mode 100644 index 000000000000..50f068091e64 --- /dev/null +++ b/cmake/Modules/CompilerRTUtils.cmake @@ -0,0 +1,17 @@ +# Because compiler-rt spends a lot of time setting up custom compile flags, +# define a handy helper function for it. The compile flags setting in CMake +# has serious issues that make its syntax challenging at best. +function(set_target_compile_flags target) + foreach(arg ${ARGN}) + set(argstring "${argstring} ${arg}") + endforeach() + set_property(TARGET ${target} PROPERTY COMPILE_FLAGS "${argstring}") +endfunction() + +function(set_target_link_flags target) + foreach(arg ${ARGN}) + set(argstring "${argstring} ${arg}") + endforeach() + set_property(TARGET ${target} PROPERTY LINK_FLAGS "${argstring}") +endfunction() + diff --git a/lib/asan/asan_interface.h b/include/sanitizer/asan_interface.h index c625a6217c0c..6afc3800f4e7 100644 --- a/lib/asan/asan_interface.h +++ b/include/sanitizer/asan_interface.h @@ -1,4 +1,4 @@ -//===-- asan_interface.h ----------------------------------------*- C++ -*-===// +//===-- sanitizer/asan_interface.h ------------------------------*- C++ -*-===// // // The LLVM Compiler Infrastructure // @@ -12,10 +12,11 @@ // This header can be included by the instrumented program to fetch // data (mostly allocator statistics) from ASan runtime library. //===----------------------------------------------------------------------===// -#ifndef ASAN_INTERFACE_H -#define ASAN_INTERFACE_H +#ifndef SANITIZER_ASAN_INTERFACE_H +#define SANITIZER_ASAN_INTERFACE_H + +#include <sanitizer/common_interface_defs.h> -#include "sanitizer_common/sanitizer_interface_defs.h" // ----------- ATTENTION ------------- // This header should NOT include any other headers from ASan runtime. // All functions in this header are extern "C" and start with __asan_. @@ -27,17 +28,13 @@ extern "C" { // before any instrumented code is executed and before any call to malloc. void __asan_init() SANITIZER_INTERFACE_ATTRIBUTE; - // This function should be called by the instrumented code. - // 'addr' is the address of a global variable called 'name' of 'size' bytes. - void __asan_register_global(uptr addr, uptr size, const char *name) - SANITIZER_INTERFACE_ATTRIBUTE; - // This structure describes an instrumented global variable. struct __asan_global { uptr beg; // The address of the global. uptr size; // The original size of the global. uptr size_with_redzone; // The size with the redzone. - const char *name; // Name as a C string. + const char *name; // Name as a C string. + uptr has_dynamic_init; // Non-zero if the global has dynamic initializer. }; // These two functions should be called by the instrumented code. @@ -47,6 +44,14 @@ extern "C" { void __asan_unregister_globals(__asan_global *globals, uptr n) SANITIZER_INTERFACE_ATTRIBUTE; + // These two functions should be called before and after dynamic initializers + // run, respectively. They should be called with parameters describing all + // dynamically initialized globals defined in the calling TU. + void __asan_before_dynamic_init(uptr first_addr, uptr last_addr) + SANITIZER_INTERFACE_ATTRIBUTE; + void __asan_after_dynamic_init() + SANITIZER_INTERFACE_ATTRIBUTE; + // These two functions are used by the instrumented code in the // use-after-return mode. __asan_stack_malloc allocates size bytes of // fake stack and __asan_stack_free poisons it. real_stack is a pointer to @@ -56,6 +61,15 @@ extern "C" { void __asan_stack_free(uptr ptr, uptr size, uptr real_stack) SANITIZER_INTERFACE_ATTRIBUTE; + // These two functions are used by instrumented code in the + // use-after-scope mode. They mark memory for local variables as + // unaddressable when they leave scope and addressable before the + // function exits. + void __asan_poison_stack_memory(uptr addr, uptr size) + SANITIZER_INTERFACE_ATTRIBUTE; + void __asan_unpoison_stack_memory(uptr addr, uptr size) + SANITIZER_INTERFACE_ATTRIBUTE; + // Marks memory region [addr, addr+size) as unaddressable. // This memory must be previously allocated by the user program. Accessing // addresses in this region from instrumented code is forbidden until @@ -98,6 +112,15 @@ extern "C" { bool __asan_address_is_poisoned(void const volatile *addr) SANITIZER_INTERFACE_ATTRIBUTE; + // If at least on byte in [beg, beg+size) is poisoned, return the address + // of the first such byte. Otherwise return 0. + uptr __asan_region_is_poisoned(uptr beg, uptr size) + SANITIZER_INTERFACE_ATTRIBUTE; + + // Print the description of addr (useful when debugging in gdb). + void __asan_describe_address(uptr addr) + SANITIZER_INTERFACE_ATTRIBUTE; + // This is an internal function that is called to report an error. // However it is still a part of the interface because users may want to // set a breakpoint on this function in a debugger. @@ -118,6 +141,21 @@ extern "C" { void __asan_set_error_report_callback(void (*callback)(const char*)) SANITIZER_INTERFACE_ATTRIBUTE; + // User may provide function that would be called right when ASan detects + // an error. This can be used to notice cases when ASan detects an error, but + // the program crashes before ASan report is printed. + /* OPTIONAL */ void __asan_on_error() + SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE; + + // User may provide its own implementation for symbolization function. + // It should print the description of instruction at address "pc" to + // "out_buffer". Description should be at most "out_size" bytes long. + // User-specified function should return true if symbolization was + // successful. + /* OPTIONAL */ bool __asan_symbolize(const void *pc, char *out_buffer, + int out_size) + SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE; + // Returns the estimated number of bytes that will be reserved by allocator // for request of "size" bytes. If ASan allocator can't allocate that much // memory, returns the maximal possible allocation size, otherwise returns @@ -154,6 +192,21 @@ extern "C" { // Prints accumulated stats to stderr. Used for debugging. void __asan_print_accumulated_stats() SANITIZER_INTERFACE_ATTRIBUTE; -} // namespace -#endif // ASAN_INTERFACE_H + // This function may be optionally provided by user and should return + // a string containing ASan runtime options. See asan_flags.h for details. + /* OPTIONAL */ const char* __asan_default_options() + SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE; + + // Malloc hooks that may be optionally provided by user. + // __asan_malloc_hook(ptr, size) is called immediately after + // allocation of "size" bytes, which returned "ptr". + // __asan_free_hook(ptr) is called immediately before + // deallocation of "ptr". + /* OPTIONAL */ void __asan_malloc_hook(void *ptr, uptr size) + SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE; + /* OPTIONAL */ void __asan_free_hook(void *ptr) + SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE; +} // extern "C" + +#endif // SANITIZER_ASAN_INTERFACE_H diff --git a/lib/sanitizer_common/sanitizer_interface_defs.h b/include/sanitizer/common_interface_defs.h index 2395ea505657..9d8fa5582b67 100644 --- a/lib/sanitizer_common/sanitizer_interface_defs.h +++ b/include/sanitizer/common_interface_defs.h @@ -1,4 +1,4 @@ -//===-- sanitizer_interface_defs.h -----------------------------*- C++ -*-===// +//===-- sanitizer/common_interface_defs.h -----------------------*- C++ -*-===// // // The LLVM Compiler Infrastructure // @@ -12,8 +12,8 @@ // NOTE: This file may be included into user code. //===----------------------------------------------------------------------===// -#ifndef SANITIZER_INTERFACE_DEFS_H -#define SANITIZER_INTERFACE_DEFS_H +#ifndef SANITIZER_COMMON_INTERFACE_DEFS_H +#define SANITIZER_COMMON_INTERFACE_DEFS_H // ----------- ATTENTION ------------- // This header should NOT include any other headers to avoid portability issues. @@ -30,6 +30,12 @@ # define SANITIZER_WEAK_ATTRIBUTE __attribute__((weak)) #endif +#ifdef __linux__ +# define SANITIZER_SUPPORTS_WEAK_HOOKS 1 +#else +# define SANITIZER_SUPPORTS_WEAK_HOOKS 0 +#endif + // __has_feature #if !defined(__has_feature) # define __has_feature(x) 0 @@ -40,8 +46,21 @@ // in a portable way by the language itself. namespace __sanitizer { +#if defined(_WIN64) +// 64-bit Windows uses LLP64 data model. +typedef unsigned long long uptr; // NOLINT +typedef signed long long sptr; // NOLINT +#else typedef unsigned long uptr; // NOLINT typedef signed long sptr; // NOLINT +#endif // defined(_WIN64) +#if defined(__x86_64__) +// Since x32 uses ILP32 data model in 64-bit hardware mode, we must use +// 64-bit pointer to unwind stack frame. +typedef unsigned long long uhwptr; // NOLINT +#else +typedef uptr uhwptr; // NOLINT +#endif typedef unsigned char u8; typedef unsigned short u16; // NOLINT typedef unsigned int u32; @@ -53,4 +72,21 @@ typedef signed long long s64; // NOLINT } // namespace __sanitizer -#endif // SANITIZER_INTERFACE_DEFS_H +extern "C" { + // Tell the tools to write their reports to "path.<pid>" instead of stderr. + void __sanitizer_set_report_path(const char *path) + SANITIZER_INTERFACE_ATTRIBUTE; + + // Tell the tools to write their reports to given file descriptor instead of + // stderr. + void __sanitizer_set_report_fd(int fd) + SANITIZER_INTERFACE_ATTRIBUTE; + + // Notify the tools that the sandbox is going to be turned on. The reserved + // parameter will be used in the future to hold a structure with functions + // that the tools may call to bypass the sandbox. + void __sanitizer_sandbox_on_notify(void *reserved) + SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE; +} // extern "C" + +#endif // SANITIZER_COMMON_INTERFACE_DEFS_H diff --git a/include/sanitizer/msan_interface.h b/include/sanitizer/msan_interface.h new file mode 100644 index 000000000000..1a76dd60599f --- /dev/null +++ b/include/sanitizer/msan_interface.h @@ -0,0 +1,124 @@ +//===-- msan_interface.h --------------------------------------------------===// +// +// 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 MemorySanitizer. +// +// Public interface header. +//===----------------------------------------------------------------------===// +#ifndef MSAN_INTERFACE_H +#define MSAN_INTERFACE_H + +#include <sanitizer/common_interface_defs.h> + +using __sanitizer::uptr; +using __sanitizer::sptr; +using __sanitizer::u32; + +#ifdef __cplusplus +extern "C" { +#endif + +// FIXME: document all interface functions. + +SANITIZER_INTERFACE_ATTRIBUTE +int __msan_get_track_origins(); + +SANITIZER_INTERFACE_ATTRIBUTE +void __msan_init(); + +// Print a warning and maybe return. +// This function can die based on flags()->exit_code. +SANITIZER_INTERFACE_ATTRIBUTE +void __msan_warning(); + +// Print a warning and die. +// Intrumentation inserts calls to this function when building in "fast" mode +// (i.e. -mllvm -msan-keep-going) +SANITIZER_INTERFACE_ATTRIBUTE __attribute__((noreturn)) +void __msan_warning_noreturn(); + +SANITIZER_INTERFACE_ATTRIBUTE +void __msan_unpoison(void *a, uptr size); +SANITIZER_INTERFACE_ATTRIBUTE +void __msan_clear_and_unpoison(void *a, uptr size); +SANITIZER_INTERFACE_ATTRIBUTE +void* __msan_memcpy(void *dst, const void *src, uptr size); +SANITIZER_INTERFACE_ATTRIBUTE +void* __msan_memset(void *s, int c, uptr n); +SANITIZER_INTERFACE_ATTRIBUTE +void* __msan_memmove(void* dest, const void* src, uptr n); +SANITIZER_INTERFACE_ATTRIBUTE +void __msan_copy_poison(void *dst, const void *src, uptr size); +SANITIZER_INTERFACE_ATTRIBUTE +void __msan_copy_origin(void *dst, const void *src, uptr size); +SANITIZER_INTERFACE_ATTRIBUTE +void __msan_move_poison(void *dst, const void *src, uptr size); +SANITIZER_INTERFACE_ATTRIBUTE +void __msan_poison(void *a, uptr size); +SANITIZER_INTERFACE_ATTRIBUTE +void __msan_poison_stack(void *a, uptr size); + +// Copy size bytes from src to dst and unpoison the result. +// Useful to implement unsafe loads. +SANITIZER_INTERFACE_ATTRIBUTE +void __msan_load_unpoisoned(void *src, uptr size, void *dst); + +// Returns the offset of the first (at least partially) poisoned byte, +// or -1 if the whole range is good. +SANITIZER_INTERFACE_ATTRIBUTE +sptr __msan_test_shadow(const void *x, uptr size); + +SANITIZER_INTERFACE_ATTRIBUTE +void __msan_set_origin(void *a, uptr size, u32 origin); +SANITIZER_INTERFACE_ATTRIBUTE +void __msan_set_alloca_origin(void *a, uptr size, const char *descr); +SANITIZER_INTERFACE_ATTRIBUTE +u32 __msan_get_origin(void *a); + +SANITIZER_INTERFACE_ATTRIBUTE +void __msan_clear_on_return(); + +// Default: -1 (don't exit on error). +SANITIZER_INTERFACE_ATTRIBUTE +void __msan_set_exit_code(int exit_code); + +SANITIZER_INTERFACE_ATTRIBUTE +int __msan_set_poison_in_malloc(int do_poison); + +// For testing. +SANITIZER_INTERFACE_ATTRIBUTE +void __msan_set_expect_umr(int expect_umr); +SANITIZER_INTERFACE_ATTRIBUTE +void __msan_break_optimization(void *x); +SANITIZER_INTERFACE_ATTRIBUTE +void __msan_print_shadow(const void *x, uptr size); +SANITIZER_INTERFACE_ATTRIBUTE +void __msan_print_param_shadow(); +SANITIZER_INTERFACE_ATTRIBUTE +int __msan_has_dynamic_component(); + +// Returns x such that %fs:x is the first byte of __msan_retval_tls. +SANITIZER_INTERFACE_ATTRIBUTE +int __msan_get_retval_tls_offset(); +SANITIZER_INTERFACE_ATTRIBUTE +int __msan_get_param_tls_offset(); + +// For testing. +SANITIZER_INTERFACE_ATTRIBUTE +u32 __msan_get_origin_tls(); +SANITIZER_INTERFACE_ATTRIBUTE +const char *__msan_get_origin_descr_if_stack(u32 id); +SANITIZER_INTERFACE_ATTRIBUTE +void __msan_partial_poison(void* data, void* shadow, uptr size); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 67019655332a..fa6d8abc65e6 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -1,34 +1,23 @@ -# Compute the Clang version from the LLVM version. -# FIXME: We should be able to reuse CLANG_VERSION variable calculated -# in Clang cmake files, instead of copying the rules here. -string(REGEX MATCH "[0-9]+\\.[0-9]+(\\.[0-9]+)?" CLANG_VERSION - ${PACKAGE_VERSION}) - -# Call add_clang_runtime_static_library(<target_library>) to make -# sure that static <target_library> is built in the directory -# where Clang driver expects to find it. -if (APPLE) - set(CLANG_RUNTIME_LIB_DIR - ${LLVM_BINARY_DIR}/lib/clang/${CLANG_VERSION}/lib/darwin) -elseif (UNIX) - # Assume Linux. - set(CLANG_RUNTIME_LIB_DIR - ${LLVM_BINARY_DIR}/lib/clang/${CLANG_VERSION}/lib/linux) -endif() -function(add_clang_runtime_static_library target_name) - set_target_properties(${target_name} PROPERTIES - ARCHIVE_OUTPUT_DIRECTORY ${CLANG_RUNTIME_LIB_DIR}) -endfunction() - # First, add the subdirectories which contain feature-based runtime libraries # and several convenience helper libraries. -add_subdirectory(asan) -add_subdirectory(interception) -add_subdirectory(sanitizer_common) +if(CMAKE_SYSTEM_NAME MATCHES "Darwin|Linux") + # AddressSanitizer is supported on Linux and Mac OS X. + # Windows support is work in progress. + add_subdirectory(asan) + add_subdirectory(interception) + add_subdirectory(sanitizer_common) + if(NOT ANDROID) + add_subdirectory(ubsan) + endif() +endif() +if("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux" AND NOT ANDROID) + # ThreadSanitizer and MemorySanitizer are supported on Linux only. + add_subdirectory(tsan) + add_subdirectory(msan) +endif() # FIXME: Add support for the profile library. - # The top-level lib directory contains a large amount of C code which provides # generic implementations of the core runtime library along with optimized # architecture-specific code in various subdirectories. @@ -163,7 +152,7 @@ set(GENERIC_SOURCES umodti3.c ) -if(CAN_TARGET_X86_64) +if(CAN_TARGET_x86_64) add_library(clang_rt.x86_64 STATIC x86_64/floatdidf.c x86_64/floatdisf.c @@ -173,9 +162,10 @@ if(CAN_TARGET_X86_64) x86_64/floatundixf.S ${GENERIC_SOURCES} ) - set_target_properties(clang_rt.x86_64 PROPERTIES COMPILE_FLAGS "-std=c99 ${TARGET_X86_64_CFLAGS}") + set_target_properties(clang_rt.x86_64 PROPERTIES COMPILE_FLAGS "-std=c99 ${TARGET_x86_64_CFLAGS}") + add_clang_compiler_rt_libraries(clang_rt.x86_64) endif() -if(CAN_TARGET_I386) +if(CAN_TARGET_i386) add_library(clang_rt.i386 STATIC i386/ashldi3.S i386/ashrdi3.S @@ -193,5 +183,6 @@ if(CAN_TARGET_I386) i386/umoddi3.S ${GENERIC_SOURCES} ) - set_target_properties(clang_rt.i386 PROPERTIES COMPILE_FLAGS "-std=c99 ${TARGET_I386_CFLAGS}") + set_target_properties(clang_rt.i386 PROPERTIES COMPILE_FLAGS "-std=c99 ${TARGET_i386_CFLAGS}") + add_clang_compiler_rt_libraries(clang_rt.i386) endif() diff --git a/lib/Makefile.mk b/lib/Makefile.mk index 791921a80068..ea471e01b1e6 100644 --- a/lib/Makefile.mk +++ b/lib/Makefile.mk @@ -19,6 +19,7 @@ SubDirs += interception SubDirs += profile SubDirs += sanitizer_common SubDirs += tsan +SubDirs += ubsan # FIXME: We don't currently support building an atomic library, and as it must # be a separate library from the runtime library, we need to remove its source diff --git a/lib/arm/Makefile.mk b/lib/arm/Makefile.mk index e7bbd7b615d5..04dec88a9714 100644 --- a/lib/arm/Makefile.mk +++ b/lib/arm/Makefile.mk @@ -9,7 +9,7 @@ ModuleName := builtins SubDirs := -OnlyArchs := armv5 armv6 armv7 +OnlyArchs := armv5 armv6 armv7 armv7f armv7k armv7s AsmSources := $(foreach file,$(wildcard $(Dir)/*.S),$(notdir $(file))) Sources := $(foreach file,$(wildcard $(Dir)/*.c),$(notdir $(file))) diff --git a/lib/arm/divsi3.S b/lib/arm/divsi3.S index 00e61815ab4f..e76fe31bb782 100644 --- a/lib/arm/divsi3.S +++ b/lib/arm/divsi3.S @@ -25,7 +25,16 @@ // Ok, APCS and AAPCS agree on 32 bit args, so it's safe to use the same routine. DEFINE_AEABI_FUNCTION_ALIAS(__aeabi_idiv, __divsi3) DEFINE_COMPILERRT_FUNCTION(__divsi3) - ESTABLISH_FRAME +#if __ARM_ARCH_7S__ + tst r1,r1 + beq LOCAL_LABEL(divzero) + sdiv r0, r0, r1 + bx lr +LOCAL_LABEL(divzero): + mov r0,#0 + bx lr +#else +ESTABLISH_FRAME // Set aside the sign of the quotient. eor r4, r0, r1 // Take absolute value of a and b via abs(x) = (x^(x >> 31)) - (x >> 31). @@ -39,3 +48,4 @@ DEFINE_COMPILERRT_FUNCTION(__divsi3) eor r0, r0, r4, asr #31 sub r0, r0, r4, asr #31 CLEAR_FRAME_AND_RETURN +#endif diff --git a/lib/arm/udivsi3.S b/lib/arm/udivsi3.S index 6d8966539c7f..28979fee4bdc 100644 --- a/lib/arm/udivsi3.S +++ b/lib/arm/udivsi3.S @@ -33,6 +33,15 @@ // Ok, APCS and AAPCS agree on 32 bit args, so it's safe to use the same routine. DEFINE_AEABI_FUNCTION_ALIAS(__aeabi_uidiv, __udivsi3) DEFINE_COMPILERRT_FUNCTION(__udivsi3) +#if __ARM_ARCH_7S__ + tst r1,r1 + beq LOCAL_LABEL(divzero) + udiv r0, r0, r1 + bx lr + LOCAL_LABEL(divzero): + mov r0,#0 + bx lr +#else // We use a simple digit by digit algorithm; before we get into the actual // divide loop, we must calculate the left-shift amount necessary to align // the MSB of the divisor with that of the dividend (If this shift is @@ -78,3 +87,4 @@ LOCAL_LABEL(return): // Move the quotient to r0 and return. mov r0, q CLEAR_FRAME_AND_RETURN +#endif diff --git a/lib/asan/CMakeLists.txt b/lib/asan/CMakeLists.txt index ce985f528172..92cba6dee622 100644 --- a/lib/asan/CMakeLists.txt +++ b/lib/asan/CMakeLists.txt @@ -2,6 +2,8 @@ set(ASAN_SOURCES asan_allocator.cc + asan_allocator2.cc + asan_fake_stack.cc asan_globals.cc asan_interceptors.cc asan_linux.cc @@ -12,7 +14,7 @@ set(ASAN_SOURCES asan_new_delete.cc asan_poisoning.cc asan_posix.cc - asan_printf.cc + asan_report.cc asan_rtl.cc asan_stack.cc asan_stats.cc @@ -21,62 +23,99 @@ set(ASAN_SOURCES asan_win.cc ) +set(ASAN_DYLIB_SOURCES + ${ASAN_SOURCES} + dynamic/asan_interceptors_dynamic.cc + ) + include_directories(..) -set(ASAN_CFLAGS - -fPIC - -fno-exceptions - -funwind-tables - -fvisibility=hidden - -fno-builtin - -fomit-frame-pointer - -O3 - ) -if (SUPPORTS_NO_VARIADIC_MACROS_FLAG) - list(APPEND ASAN_CFLAGS -Wno-variadic-macros) -endif () +set(ASAN_CFLAGS ${SANITIZER_COMMON_CFLAGS}) -if (APPLE) - list(APPEND ASAN_CFLAGS -mmacosx-version-min=10.5) +if(ANDROID) + set(ASAN_COMMON_DEFINITIONS + ASAN_HAS_EXCEPTIONS=1 + ASAN_FLEXIBLE_MAPPING_AND_OFFSET=0 + ASAN_NEEDS_SEGV=0 + ASAN_LOW_MEMORY=1 + ) +else() + set(ASAN_COMMON_DEFINITIONS + ASAN_HAS_EXCEPTIONS=1 + ASAN_FLEXIBLE_MAPPING_AND_OFFSET=0 + ASAN_NEEDS_SEGV=1 + ) endif() -set(ASAN_COMMON_DEFINITIONS - ASAN_HAS_EXCEPTIONS=1 - ASAN_NEEDS_SEGV=1 +set(ASAN_DYLIB_DEFINITIONS + ${ASAN_COMMON_DEFINITIONS} + MAC_INTERPOSE_FUNCTIONS=1 ) -# FIXME: We need to build universal binaries on OS X instead of -# two arch-specific binaries. +# Architectures supported by ASan. +filter_available_targets(ASAN_SUPPORTED_ARCH + x86_64 i386) -if(CAN_TARGET_X86_64) - add_library(clang_rt.asan-x86_64 STATIC +set(ASAN_RUNTIME_LIBRARIES) +if(APPLE) + # Build universal binary on APPLE. + add_library(clang_rt.asan_osx STATIC ${ASAN_SOURCES} - $<TARGET_OBJECTS:RTInterception.x86_64> - $<TARGET_OBJECTS:RTSanitizerCommon.x86_64> - ) - set_target_compile_flags(clang_rt.asan-x86_64 - ${ASAN_CFLAGS} - ${TARGET_X86_64_CFLAGS} + $<TARGET_OBJECTS:RTInterception.osx> + $<TARGET_OBJECTS:RTSanitizerCommon.osx> ) - set_property(TARGET clang_rt.asan-x86_64 APPEND PROPERTY COMPILE_DEFINITIONS - ${ASAN_COMMON_DEFINITIONS}) - add_clang_runtime_static_library(clang_rt.asan-x86_64) -endif() -if(CAN_TARGET_I386) - add_library(clang_rt.asan-i386 STATIC + set_target_compile_flags(clang_rt.asan_osx ${ASAN_CFLAGS}) + set_target_properties(clang_rt.asan_osx PROPERTIES + OSX_ARCHITECTURES "${ASAN_SUPPORTED_ARCH}") + list(APPEND ASAN_RUNTIME_LIBRARIES clang_rt.asan_osx) +elseif(ANDROID) + add_library(clang_rt.asan-arm-android SHARED ${ASAN_SOURCES} - $<TARGET_OBJECTS:RTInterception.i386> - $<TARGET_OBJECTS:RTSanitizerCommon.i386> + $<TARGET_OBJECTS:RTInterception.arm.android> + $<TARGET_OBJECTS:RTSanitizerCommon.arm.android> ) - set_target_compile_flags(clang_rt.asan-i386 + set_target_compile_flags(clang_rt.asan-arm-android ${ASAN_CFLAGS} - ${TARGET_I386_CFLAGS} ) - set_property(TARGET clang_rt.asan-i386 APPEND PROPERTY COMPILE_DEFINITIONS - ${ASAN_COMMON_DEFINITIONS}) - add_clang_runtime_static_library(clang_rt.asan-i386) + target_link_libraries(clang_rt.asan-arm-android dl) + list(APPEND ASAN_RUNTIME_LIBRARIES clang_rt.asan-arm-android) +else() + # Otherwise, build separate libraries for each target. + foreach(arch ${ASAN_SUPPORTED_ARCH}) + add_library(clang_rt.asan-${arch} STATIC + ${ASAN_SOURCES} + $<TARGET_OBJECTS:RTInterception.${arch}> + $<TARGET_OBJECTS:RTSanitizerCommon.${arch}>) + set_target_compile_flags(clang_rt.asan-${arch} + ${ASAN_CFLAGS} ${TARGET_${arch}_CFLAGS}) + list(APPEND ASAN_RUNTIME_LIBRARIES clang_rt.asan-${arch}) + endforeach() endif() +set_property(TARGET ${ASAN_RUNTIME_LIBRARIES} APPEND PROPERTY + COMPILE_DEFINITIONS ${ASAN_COMMON_DEFINITIONS}) +add_clang_compiler_rt_libraries(${ASAN_RUNTIME_LIBRARIES}) + +set(ASAN_DYNAMIC_RUNTIME_LIBRARIES) +if(APPLE) + # Build universal binary on APPLE. + add_library(clang_rt.asan_osx_dynamic SHARED + ${ASAN_DYLIB_SOURCES} + $<TARGET_OBJECTS:RTInterception.osx> + $<TARGET_OBJECTS:RTSanitizerCommon.osx> + ) + set_target_compile_flags(clang_rt.asan_osx_dynamic ${ASAN_CFLAGS}) + set_target_properties(clang_rt.asan_osx_dynamic PROPERTIES + COMPILE_DEFINITIONS "${ASAN_DYLIB_DEFINITIONS}" + OSX_ARCHITECTURES "${ASAN_SUPPORTED_ARCH}" + LINK_FLAGS "-framework Foundation") + list(APPEND ASAN_DYNAMIC_RUNTIME_LIBRARIES clang_rt.asan_osx_dynamic) +endif() +add_clang_compiler_rt_libraries(${ASAN_DYNAMIC_RUNTIME_LIBRARIES}) + + if(LLVM_INCLUDE_TESTS) add_subdirectory(tests) endif() + +add_subdirectory(lit_tests) diff --git a/lib/asan/Makefile.mk b/lib/asan/Makefile.mk index 9d1a2e8a9a28..af9602e8b242 100644 --- a/lib/asan/Makefile.mk +++ b/lib/asan/Makefile.mk @@ -8,7 +8,7 @@ #===------------------------------------------------------------------------===# ModuleName := asan -SubDirs := +SubDirs := dynamic Sources := $(foreach file,$(wildcard $(Dir)/*.cc),$(notdir $(file))) ObjNames := $(Sources:%.cc=%.o) @@ -17,8 +17,9 @@ Implementation := Generic # FIXME: use automatic dependencies? Dependencies := $(wildcard $(Dir)/*.h) -Dependencies += $(wildcard $(Dir)/interception/*.h) -Dependencies += $(wildcard $(Dir)/interception/mach_override/*.h) +Dependencies += $(wildcard $(Dir)/../interception/*.h) +Dependencies += $(wildcard $(Dir)/../interception/mach_override/*.h) +Dependencies += $(wildcard $(Dir)/../sanitizer_common/*.h) # Define a convenience variable for all the asan functions. AsanFunctions := $(Sources:%.cc=%) diff --git a/lib/asan/Makefile.old b/lib/asan/Makefile.old deleted file mode 100644 index 4ab80e20bcb1..000000000000 --- a/lib/asan/Makefile.old +++ /dev/null @@ -1,349 +0,0 @@ -#===- lib/asan/Makefile.old --------------------------------*- Makefile -*--===# -# -# The LLVM Compiler Infrastructure -# -# This file is distributed under the University of Illinois Open Source -# License. See LICENSE.TXT for details. -# -#===------------------------------------------------------------------------===# - -OS=$(shell uname | tr '[A-Z]' '[a-z]') -ROOT=$(shell pwd) -MAKEFILE=Makefile.old # this file. - -ifeq ($(ARCH), android) - ANDROID_CFLAGS= \ - -DANDROID \ - -D__WORDSIZE=32 \ - -I$(ANDROID_BUILD_TOP)/external/stlport/stlport \ - -I$(ANDROID_BUILD_TOP)/bionic \ - -I$(ANDROID_BUILD_TOP)/bionic/libstdc++/include \ - -I$(ANDROID_BUILD_TOP)/bionic/libc/arch-arm/include \ - -I$(ANDROID_BUILD_TOP)/bionic/libc/include \ - -I$(ANDROID_BUILD_TOP)/bionic/libc/kernel/common \ - -I$(ANDROID_BUILD_TOP)/bionic/libc/kernel/arch-arm \ - -I$(ANDROID_BUILD_TOP)/bionic/libm/include \ - -I$(ANDROID_BUILD_TOP)/bionic/libm/include/arm \ - -I$(ANDROID_BUILD_TOP)/bionic/libthread_db/include \ - -L$(ANDROID_PRODUCT_OUT)/obj/lib - CLANG_FLAGS= \ - -ccc-host-triple arm-linux-androideabi \ - -D__compiler_offsetof=__builtin_offsetof \ - -D__ELF__=1 \ - -ccc-gcc-name arm-linux-androideabi-g++ \ - $(ANDROID_CFLAGS) - CC=$(ANDROID_EABI_TOOLCHAIN)/arm-linux-androideabi-gcc $(ANDROID_CFLAGS) - CXX=$(ANDROID_EABI_TOOLCHAIN)/arm-linux-androideabi-g++ $(ANDROID_CFLAGS) -endif - -ifeq ($(ARCH), arm) - # Example make command line: - # CROSSTOOL=$HOME/x-tools/arm-unknown-linux-gnueabi/ PATH=$CROSSTOOL/bin:$PATH make ARCH=arm asan_test - CLANG_FLAGS= \ - -ccc-host-triple arm-unknown-linux-gnueabi \ - -march=armv7-a -mfloat-abi=softfp -mfp=neon \ - -ccc-gcc-name arm-unknown-linux-gnueabi-g++ \ - -B$(CROSSTOOL)/lib/gcc/arm-unknown-linux-gnueabi/4.4.4 \ - -B$(CROSSTOOL)/arm-unknown-linux-gnueabi/sys-root/usr/lib \ - -I$(CROSSTOOL)/lib/gcc/arm-unknown-linux-gnueabi/4.4.4/include \ - -I$(CROSSTOOL)/arm-unknown-linux-gnueabi/include/c++/4.4.4 \ - -I$(CROSSTOOL)/arm-unknown-linux-gnueabi/include/c++/4.4.4/arm-unknown-linux-gnueabi \ - -I$(CROSSTOOL)/arm-unknown-linux-gnueabi/sys-root/include \ - -I$(CROSSTOOL)/arm-unknown-linux-gnueabi/sys-root/usr/include \ - -L$(CROSSTOOL)/lib/gcc/arm-unknown-linux-gnueabi/4.4.4 \ - -L$(CROSSTOOL)/arm-unknown-linux-gnueabi/sys-root/lib \ - -L$(CROSSTOOL)/arm-unknown-linux-gnueabi/sys-root/usr/lib - CC=$(CROSSTOOL)/bin/arm-unknown-linux-gnueabi-gcc - CXX=$(CROSSTOOL)/bin/arm-unknown-linux-gnueabi-g++ -endif - -CLANG_FLAGS= -CLANG_VERSION=3.2 -CLANG_BUILD=$(ROOT)/../../../../build/Release+Asserts -CLANG_CC=$(CLANG_BUILD)/bin/clang $(CLANG_FLAGS) -CLANG_CXX=$(CLANG_BUILD)/bin/clang++ $(CLANG_FLAGS) -FILE_CHECK=$(CLANG_BUILD)/bin/FileCheck - -CC=$(CLANG_CC) -CXX=$(CLANG_CXX) - -CFLAGS:=-Wall -fvisibility=hidden - -CLEANROOM_CXX=$(CXX) -Wall - -INSTALL_DIR=../asan_clang_$(OS) -BIN=bin_$(OS) - -LIBS=#-lpthread -ldl -ARCH=x86_64 - -ASAN_STACK=1 -ASAN_GLOBALS=1 -ASAN_SCALE=0 # default will be used -ASAN_OFFSET=-1 #default will be used -ASAN_UAR=0 -ASAN_HAS_EXCEPTIONS=1 -ASAN_FLEXIBLE_MAPPING_AND_OFFSET=0 -ASAN_HAS_BLACKLIST=1 -ASAN_NEEDS_SEGV=1 -ASAN_PIE=0 - -ifeq ($(ARCH), i386) -BITS=32 -SUFF=$(BITS) -CFLAGS:=$(CFLAGS) -m$(BITS) -endif - -ifeq ($(ARCH), x86_64) -BITS=64 -SUFF=$(BITS) -CFLAGS:=$(CFLAGS) -m$(BITS) -endif - -ifeq ($(ARCH), arm) -BITS=32 -SUFF=_arm -CFLAGS:=$(CFLAGS) -march=armv7-a -ASAN_HAS_EXCEPTIONS=0 -endif - -ifeq ($(ARCH), android) -BITS=32 -SUFF=_android -CFLAGS:=$(CFLAGS) -ASAN_HAS_EXCEPTIONS=0 -endif - -PIE= -ifeq ($(ASAN_PIE), 1) - PIE=-fPIE -pie -endif - -# This will build libasan on linux for both x86_64 and i386 in the -# desired location. The Mac library is already build by the clang's make. -# $(CLANG_BUILD)/lib/clang/$(CLANG_VERSION)/lib/$(OS)/libclang_rt.asan-$(ARCH).a -LIBASAN_INST_DIR=$(CLANG_BUILD)/lib/clang/$(CLANG_VERSION)/lib/$(OS) -LIBASAN_A=$(LIBASAN_INST_DIR)/libclang_rt.asan-$(ARCH).a - -BLACKLIST= -ifeq ($(ASAN_HAS_BLACKLIST), 1) - BLACKLIST=-mllvm -asan-blacklist=$(ROOT)/tests/asan_test.ignore -endif - -COMMON_ASAN_DEFINES=\ - -DASAN_UAR=$(ASAN_UAR) \ - -DASAN_HAS_EXCEPTIONS=$(ASAN_HAS_EXCEPTIONS) \ - -DASAN_NEEDS_SEGV=$(ASAN_NEEDS_SEGV) \ - -DASAN_HAS_BLACKLIST=$(ASAN_HAS_BLACKLIST) - -CLANG_ASAN_CXX=$(CLANG_CXX) \ - -faddress-sanitizer \ - $(BLACKLIST) \ - -mllvm -asan-stack=$(ASAN_STACK) \ - -mllvm -asan-globals=$(ASAN_GLOBALS) \ - -mllvm -asan-mapping-scale=$(ASAN_SCALE) \ - -mllvm -asan-mapping-offset-log=$(ASAN_OFFSET) \ - -mllvm -asan-use-after-return=$(ASAN_UAR) \ - $(COMMON_ASAN_DEFINES) - -CLANG_ASAN_LD=$(CLANG_CXX) -faddress-sanitizer - -GCC_ASAN_PATH=SET_FROM_COMMAND_LINE -GCC_ASAN_CXX=$(GCC_ASAN_PATH)/g++ \ - -faddress-sanitizer \ - $(COMMON_ASAN_DEFINES) - -GCC_ASAN_LD=$(GCC_ASAN_PATH)/g++ -ldl -lpthread - -ASAN_COMPILER=clang - -ifeq ($(ASAN_COMPILER), clang) - ASAN_CXX=$(CLANG_ASAN_CXX) - ASAN_LD=$(CLANG_ASAN_LD) - ASAN_LD_TAIL= -endif - -ifeq ($(ASAN_COMPILER), gcc) - ASAN_CXX=$(GCC_ASAN_CXX) - ASAN_LD=$(GCC_ASAN_LD) - ASAN_LD_TAIL=$(LIBASAN_A) -endif - -INTERCEPTION=../interception -MACH_OVERRIDE=$(INTERCEPTION)/mach_override -COMMON=../sanitizer_common - -RTL_HDR=$(wildcard *.h) \ - $(wildcard $(INTERCEPTION)/*.h) \ - $(wildcard $(MACH_OVERRIDE)/*.h) \ - $(wildcard $(COMMON)/*.h) - -LIBTSAN_SRC=$(wildcard *.cc) -INTERCEPTION_SRC=$(wildcard $(INTERCEPTION)/*.cc) -MACH_OVERRIDE_SRC=$(wildcard $(MACH_OVERRIDE)/*.c) -COMMON_SRC=$(wildcard $(COMMON)/*.cc) - - -LIBASAN_OBJ=$(patsubst %.cc,$(BIN)/%$(SUFF).o,$(LIBTSAN_SRC)) \ - $(patsubst $(INTERCEPTION)/%.cc,$(BIN)/%$(SUFF).o,$(INTERCEPTION_SRC)) \ - $(patsubst $(COMMON)/%.cc,$(BIN)/%$(SUFF).o,$(COMMON_SRC)) \ - $(patsubst $(MACH_OVERRIDE)/%.c,$(BIN)/%$(SUFF).o,$(MACH_OVERRIDE_SRC)) - -GTEST_ROOT=third_party/googletest -GTEST_INCLUDE=-I$(GTEST_ROOT)/include -GTEST_MAKE_DIR=$(GTEST_ROOT)/make-$(OS)$(SUFF) -GTEST_LIB=$(GTEST_MAKE_DIR)/gtest-all.o - -all: b64 b32 - -test: t64 t32 output_tests lint - @echo "ALL TESTS PASSED" - -output_tests: b32 b64 - cd output_tests && ./test_output.sh $(CLANG_CXX) $(CLANG_CC) $(FILE_CHECK) - -t64: b64 - $(BIN)/asan_test64 -t32: b32 - $(BIN)/asan_test32 - -b64: | mk_bin_dir - $(MAKE) -f $(MAKEFILE) ARCH=x86_64 asan_test asan_benchmarks -b32: | mk_bin_dir - $(MAKE) -f $(MAKEFILE) ARCH=i386 asan_test asan_benchmarks - -lib64: - $(MAKE) -f $(MAKEFILE) ARCH=x86_64 lib -lib32: - $(MAKE) -f $(MAKEFILE) ARCH=i386 lib - -mk_bin_dir: - mkdir -p $(BIN) - -clang: - cd ../ && llvm/rebuild_clang_and_asan.sh > /dev/null - -install: install_clang - -$(INSTALL_DIR): - mkdir -p $(INSTALL_DIR) $(INSTALL_DIR)/bin $(INSTALL_DIR)/lib - -install_clang: | $(INSTALL_DIR) - cp -v $(CLANG_BUILD)/bin/clang $(INSTALL_DIR)/bin - cp -rv $(CLANG_BUILD)/lib/clang $(INSTALL_DIR)/lib - (cd $(INSTALL_DIR)/bin; ln -sf clang clang++) - -#install_lib: | $(INSTALL_DIR) -# cp -v $(CLANG_BUILD)/lib/libasan*.a $(INSTALL_DIR)/lib - -$(BIN)/asan_noinst_test$(SUFF).o: tests/asan_noinst_test.cc $(RTL_HDR) $(MAKEFILE) - $(CLEANROOM_CXX) $(PIE) $(CFLAGS) $(GTEST_INCLUDE) -I. -I.. -g -c $< -O2 -o $@ - -$(BIN)/asan_break_optimization$(SUFF).o: tests/asan_break_optimization.cc $(MAKEFILE) - $(CLEANROOM_CXX) $(PIE) $(CFLAGS) -c $< -O0 -o $@ - -$(BIN)/%_test$(SUFF).o: tests/%_test.cc $(RTL_HDR) $(MAKEFILE) - $(ASAN_CXX) $(GTEST_INCLUDE) -I. -I.. -g -c $< -O2 -o $@ $(PIE) $(CFLAGS) - -$(BIN)/%_test$(SUFF).o: tests/%_test.mm $(RTL_HDR) $(MAKEFILE) - $(ASAN_CXX) $(GTEST_INCLUDE) -I. -I.. -g -c $< -O2 -o $@ -ObjC $(PIE) $(CFLAGS) - -RTL_COMMON_FLAGS=$(PIE) $(CFLAGS) -fPIC -c -O2 -fno-exceptions -funwind-tables \ - -Ithird_party -I.. $(ASAN_FLAGS) - -$(BIN)/%$(SUFF).o: $(INTERCEPTION)/%.cc $(RTL_HDR) $(MAKEFILE) - $(CXX) $(RTL_COMMON_FLAGS) -o $@ -g $< - -$(BIN)/%$(SUFF).o: $(COMMON)/%.cc $(RTL_HDR) $(MAKEFILE) - $(CXX) $(RTL_COMMON_FLAGS) -o $@ -g $< - -$(BIN)/%$(SUFF).o: $(MACH_OVERRIDE)/%.c $(RTL_HDR) $(MAKEFILE) - $(CC) $(RTL_COMMON_FLAGS) -o $@ -g $< - -$(BIN)/%$(SUFF).o: %.cc $(RTL_HDR) $(MAKEFILE) - $(CXX) $(RTL_COMMON_FLAGS) -o $@ -g $< \ - -DASAN_NEEDS_SEGV=$(ASAN_NEEDS_SEGV) \ - -DASAN_HAS_EXCEPTIONS=$(ASAN_HAS_EXCEPTIONS) \ - -DASAN_FLEXIBLE_MAPPING_AND_OFFSET=$(ASAN_FLEXIBLE_MAPPING_AND_OFFSET) - -$(BIN)/%$(SUFF).o: %.c $(RTL_HDR) $(MAKEFILE) - $(CC) $(PIE) $(CFLAGS) -fPIC -c -O2 -o $@ -g $< -Ithird_party \ - $(ASAN_FLAGS) - -ifeq ($(OS),darwin) -LD_FLAGS=-framework Foundation -else -LD_FLAGS= -endif - -lib: $(LIBASAN_A) - -$(LIBASAN_A): mk_bin_dir $(LIBASAN_OBJ) $(MAKEFILE) - mkdir -p $(LIBASAN_INST_DIR) - ar ru $@ $(LIBASAN_OBJ) - $(CXX) -shared $(CFLAGS) $(LIBASAN_OBJ) $(LD_FLAGS) -o $(BIN)/libasan$(SUFF).so - -TEST_OBJECTS_COMMON=\ - $(BIN)/asan_globals_test$(SUFF).o \ - $(BIN)/asan_break_optimization$(SUFF).o \ - $(BIN)/asan_noinst_test$(SUFF).o \ - $(BIN)/asan_test$(SUFF).o - -BENCHMARK_OBJECTS=\ - $(BIN)/asan_benchmarks_test$(SUFF).o \ - $(BIN)/asan_break_optimization$(SUFF).o - -ifeq ($(OS),darwin) -TEST_OBJECTS=$(TEST_OBJECTS_COMMON) \ - $(BIN)/asan_mac_test$(SUFF).o -else -TEST_OBJECTS=$(TEST_OBJECTS_COMMON) -endif - -$(BIN)/asan_test$(SUFF): $(TEST_OBJECTS) $(LIBASAN_A) $(MAKEFILE) tests/asan_test.ignore $(GTEST_LIB) - $(ASAN_LD) $(PIE) $(CFLAGS) -g -O3 $(TEST_OBJECTS) \ - $(LD_FLAGS) -o $@ $(LIBS) $(GTEST_LIB) $(ASAN_LD_TAIL) - -$(BIN)/asan_benchmarks$(SUFF): $(BENCHMARK_OBJECTS) $(LIBASAN_A) $(MAKEFILE) $(GTEST_LIB) - $(ASAN_LD) $(PIE) $(CFLAGS) -g -O3 $(BENCHMARK_OBJECTS) \ - $(LD_FLAGS) -o $@ $(LIBS) $(GTEST_LIB) $(ASAN_LD_TAIL) - -asan_test: $(BIN)/asan_test$(SUFF) - -asan_benchmarks: $(BIN)/asan_benchmarks$(SUFF) - -# for now, build gtest with clang/asan even if we use a different compiler. -$(GTEST_LIB): - mkdir -p $(GTEST_MAKE_DIR) && \ - cd $(GTEST_MAKE_DIR) && \ - $(MAKE) -f ../make/Makefile CXXFLAGS="$(PIE) $(CFLAGS) -g -w" \ - CXX="$(CLANG_CXX)" - -RTL_LINT_FILTER=-readability/casting,-readability/check,-build/include,-build/header_guard,-build/class,-legal/copyright,-build/namespaces -# TODO(kcc): remove these filters one by one -TEST_LINT_FILTER=-readability/casting,-build/include,-legal/copyright,-whitespace/newline,-runtime/sizeof,-runtime/int,-runtime/printf - -LLVM_LINT_FILTER=-,+whitespace - -ADDRESS_SANITIZER_CPP=../../../../lib/Transforms/Instrumentation/AddressSanitizer.cpp - -lint: - third_party/cpplint/cpplint.py --filter=$(LLVM_LINT_FILTER) $(ADDRESS_SANITIZER_CPP) - third_party/cpplint/cpplint.py --filter=$(RTL_LINT_FILTER) asan_*.cc asan_*.h - third_party/cpplint/cpplint.py --filter=$(RTL_LINT_FILTER) $(INTERCEPTION)/interception*.h $(INTERCEPTION)/interception*.cc - third_party/cpplint/cpplint.py --filter=$(RTL_LINT_FILTER) $(COMMON)/sanitizer_*.h $(COMMON)/sanitizer_*.cc - third_party/cpplint/cpplint.py --filter=$(TEST_LINT_FILTER) tests/*.cc output_tests/*.cc - -get_third_party: - rm -rf third_party - mkdir third_party - (cd third_party && \ - svn co -r375 http://googletest.googlecode.com/svn/trunk googletest && \ - svn co -r69 http://google-styleguide.googlecode.com/svn/trunk/cpplint cpplint \ - ) - -clean: - rm -f *.o *.ll *.S *.a *.log asan_test64* asan_test32* a.out perf.data log - rm -f $(LIBASAN_INST_DIR)/libclang_rt.asan-*.a - rm -rf $(BIN) - rm -rf $(GTEST_ROOT)/make-* diff --git a/lib/asan/README.txt b/lib/asan/README.txt index 5e6600489a69..e4f4961c5d4f 100644 --- a/lib/asan/README.txt +++ b/lib/asan/README.txt @@ -4,22 +4,26 @@ This directory contains sources of the AddressSanitizer (asan) run-time library. We are in the process of integrating AddressSanitizer with LLVM, stay tuned. Directory structre: - README.txt : This file. -Makefile.mk : Currently a stub for a proper makefile. not usable. -Makefile.old : Old out-of-tree makefile, the only usable one so far. +Makefile.mk : File for make-based build. +CMakeLists.txt : File for cmake-based build. asan_*.{cc,h} : Sources of the asan run-time lirbary. -mach_override/* : Utility to override functions on Darwin (MIT License). scripts/* : Helper scripts. +tests/* : ASan unit tests. +lit_tests/* : ASan output tests. -Temporary build instructions (verified on linux): +Also ASan runtime needs the following libraries: +lib/interception/ : Machinery used to intercept function calls. +lib/sanitizer_common/ : Code shared between ASan and TSan. -cd lib/asan -make -f Makefile.old get_third_party # gets googletest and cpplint -make -f Makefile.old test -j 8 CLANG_BUILD=/path/to/Release+Asserts -# Optional: -# make -f Makefile.old install # installs clang and rt to lib/asan_clang_linux +Currently ASan runtime can be built by both make and cmake build systems. +(see compiler-rt/make and files Makefile.mk for make-based build and +files CMakeLists.txt for cmake-based build). -For more info see http://code.google.com/p/address-sanitizer/ +ASan unit and output tests work only with cmake. You may run this +command from the root of your cmake build tree: +make check-asan +For more instructions see: +http://code.google.com/p/address-sanitizer/wiki/HowToBuild diff --git a/lib/asan/asan_allocator.cc b/lib/asan/asan_allocator.cc index 352cce00fbee..30dd4ceddd88 100644 --- a/lib/asan/asan_allocator.cc +++ b/lib/asan/asan_allocator.cc @@ -24,21 +24,19 @@ // Once freed, the body of the chunk contains the stack trace of the free call. // //===----------------------------------------------------------------------===// - #include "asan_allocator.h" + +#if ASAN_ALLOCATOR_VERSION == 1 #include "asan_interceptors.h" -#include "asan_interface.h" #include "asan_internal.h" -#include "asan_lock.h" #include "asan_mapping.h" #include "asan_stats.h" +#include "asan_report.h" #include "asan_thread.h" #include "asan_thread_registry.h" +#include "sanitizer/asan_interface.h" #include "sanitizer_common/sanitizer_atomic.h" - -#if defined(_WIN32) && !defined(__clang__) -#include <intrin.h> -#endif +#include "sanitizer_common/sanitizer_mutex.h" namespace __asan { @@ -57,43 +55,7 @@ static const uptr kMallocSizeClassStepLog = 26; static const uptr kMallocSizeClassStep = 1UL << kMallocSizeClassStepLog; static const uptr kMaxAllowedMallocSize = - (__WORDSIZE == 32) ? 3UL << 30 : 8UL << 30; - -static inline bool IsAligned(uptr a, uptr alignment) { - return (a & (alignment - 1)) == 0; -} - -static inline uptr Log2(uptr x) { - CHECK(IsPowerOfTwo(x)); -#if !defined(_WIN32) || defined(__clang__) - return __builtin_ctzl(x); -#elif defined(_WIN64) - unsigned long ret; // NOLINT - _BitScanForward64(&ret, x); - return ret; -#else - unsigned long ret; // NOLINT - _BitScanForward(&ret, x); - return ret; -#endif -} - -static inline uptr RoundUpToPowerOfTwo(uptr size) { - CHECK(size); - if (IsPowerOfTwo(size)) return size; - - unsigned long up; // NOLINT -#if !defined(_WIN32) || defined(__clang__) - up = __WORDSIZE - 1 - __builtin_clzl(size); -#elif defined(_WIN64) - _BitScanReverse64(&up, size); -#else - _BitScanReverse(&up, size); -#endif - CHECK(size < (1ULL << (up + 1))); - CHECK(size > (1ULL << up)); - return 1UL << (up + 1); -} + (SANITIZER_WORDSIZE == 32) ? 3UL << 30 : 8UL << 30; static inline uptr SizeClassToSize(u8 size_class) { CHECK(size_class < kNumberOfSizeClasses); @@ -131,7 +93,7 @@ static void PoisonHeapPartialRightRedzone(uptr mem, uptr size) { } static u8 *MmapNewPagesAndPoisonShadow(uptr size) { - CHECK(IsAligned(size, kPageSize)); + CHECK(IsAligned(size, GetPageSizeCached())); u8 *res = (u8*)MmapOrDie(size, __FUNCTION__); PoisonShadow((uptr)res, size, kAsanHeapLeftRedzoneMagic); if (flags()->debug) { @@ -166,7 +128,8 @@ struct ChunkBase { // Second 8 bytes. uptr alignment_log : 8; - uptr used_size : FIRST_32_SECOND_64(32, 56); // Size requested by the user. + uptr alloc_type : 2; + uptr used_size : FIRST_32_SECOND_64(32, 54); // Size requested by the user. // This field may overlap with the user area and thus should not // be used while the chunk is in CHUNK_ALLOCATED state. @@ -198,50 +161,23 @@ struct AsanChunk: public ChunkBase { if (REDZONE < sizeof(ChunkBase)) return 0; return (REDZONE) / sizeof(u32); } +}; - bool AddrIsInside(uptr addr, uptr access_size, uptr *offset) { - if (addr >= Beg() && (addr + access_size) <= (Beg() + used_size)) { - *offset = addr - Beg(); - return true; - } - return false; - } - - bool AddrIsAtLeft(uptr addr, uptr access_size, uptr *offset) { - if (addr < Beg()) { - *offset = Beg() - addr; - return true; - } - return false; - } +uptr AsanChunkView::Beg() { return chunk_->Beg(); } +uptr AsanChunkView::End() { return Beg() + UsedSize(); } +uptr AsanChunkView::UsedSize() { return chunk_->used_size; } +uptr AsanChunkView::AllocTid() { return chunk_->alloc_tid; } +uptr AsanChunkView::FreeTid() { return chunk_->free_tid; } - bool AddrIsAtRight(uptr addr, uptr access_size, uptr *offset) { - if (addr + access_size >= Beg() + used_size) { - if (addr <= Beg() + used_size) - *offset = 0; - else - *offset = addr - (Beg() + used_size); - return true; - } - return false; - } +void AsanChunkView::GetAllocStack(StackTrace *stack) { + StackTrace::UncompressStack(stack, chunk_->compressed_alloc_stack(), + chunk_->compressed_alloc_stack_size()); +} - void DescribeAddress(uptr addr, uptr access_size) { - uptr offset; - AsanPrintf("%p is located ", (void*)addr); - if (AddrIsInside(addr, access_size, &offset)) { - AsanPrintf("%zu bytes inside of", offset); - } else if (AddrIsAtLeft(addr, access_size, &offset)) { - AsanPrintf("%zu bytes to the left of", offset); - } else if (AddrIsAtRight(addr, access_size, &offset)) { - AsanPrintf("%zu bytes to the right of", offset); - } else { - AsanPrintf(" somewhere around (this is AddressSanitizer bug!)"); - } - AsanPrintf(" %zu-byte region [%p,%p)\n", - used_size, (void*)Beg(), (void*)(Beg() + used_size)); - } -}; +void AsanChunkView::GetFreeStack(StackTrace *stack) { + StackTrace::UncompressStack(stack, chunk_->compressed_free_stack(), + chunk_->compressed_free_stack_size()); +} static AsanChunk *PtrToChunk(uptr ptr) { AsanChunk *m = (AsanChunk*)(ptr - REDZONE); @@ -251,37 +187,15 @@ static AsanChunk *PtrToChunk(uptr ptr) { return m; } - void AsanChunkFifoList::PushList(AsanChunkFifoList *q) { CHECK(q->size() > 0); - if (last_) { - CHECK(first_); - CHECK(!last_->next); - last_->next = q->first_; - last_ = q->last_; - } else { - CHECK(!first_); - last_ = q->last_; - first_ = q->first_; - CHECK(first_); - } - CHECK(last_); - CHECK(!last_->next); size_ += q->size(); + append_back(q); q->clear(); } void AsanChunkFifoList::Push(AsanChunk *n) { - CHECK(n->next == 0); - if (last_) { - CHECK(first_); - CHECK(!last_->next); - last_->next = n; - last_ = n; - } else { - CHECK(!first_); - last_ = first_ = n; - } + push_back(n); size_ += n->Size(); } @@ -290,15 +204,9 @@ void AsanChunkFifoList::Push(AsanChunk *n) { // ago. Not sure if we can or want to do anything with this. AsanChunk *AsanChunkFifoList::Pop() { CHECK(first_); - AsanChunk *res = first_; - first_ = first_->next; - if (first_ == 0) - last_ = 0; - CHECK(size_ >= res->Size()); + AsanChunk *res = front(); size_ -= res->Size(); - if (last_) { - CHECK(!last_->next); - } + pop_front(); return res; } @@ -315,14 +223,13 @@ struct PageGroup { class MallocInfo { public: - explicit MallocInfo(LinkerInitialized x) : mu_(x) { } AsanChunk *AllocateChunks(u8 size_class, uptr n_chunks) { AsanChunk *m = 0; AsanChunk **fl = &free_lists_[size_class]; { - ScopedLock lock(&mu_); + BlockingMutexLock lock(&mu_); for (uptr i = 0; i < n_chunks; i++) { if (!(*fl)) { *fl = GetNewChunks(size_class); @@ -340,7 +247,7 @@ class MallocInfo { void SwallowThreadLocalMallocStorage(AsanThreadLocalMallocStorage *x, bool eat_free_lists) { CHECK(flags()->quarantine_size > 0); - ScopedLock lock(&mu_); + BlockingMutexLock lock(&mu_); AsanChunkFifoList *q = &x->quarantine_; if (q->size() > 0) { quarantine_.PushList(q); @@ -364,23 +271,24 @@ class MallocInfo { } void BypassThreadLocalQuarantine(AsanChunk *chunk) { - ScopedLock lock(&mu_); + BlockingMutexLock lock(&mu_); quarantine_.Push(chunk); } - AsanChunk *FindMallocedOrFreed(uptr addr, uptr access_size) { - ScopedLock lock(&mu_); - return FindChunkByAddr(addr); + AsanChunk *FindChunkByAddr(uptr addr) { + BlockingMutexLock lock(&mu_); + return FindChunkByAddrUnlocked(addr); } uptr AllocationSize(uptr ptr) { if (!ptr) return 0; - ScopedLock lock(&mu_); + BlockingMutexLock lock(&mu_); + + // Make sure this is our chunk and |ptr| actually points to the beginning + // of the allocated memory. + AsanChunk *m = FindChunkByAddrUnlocked(ptr); + if (!m || m->Beg() != ptr) return 0; - // first, check if this is our memory - PageGroup *g = FindPageGroupUnlocked(ptr); - if (!g) return 0; - AsanChunk *m = PtrToChunk(ptr); if (m->chunk_state == CHUNK_ALLOCATED) { return m->used_size; } else { @@ -397,7 +305,7 @@ class MallocInfo { } void PrintStatus() { - ScopedLock lock(&mu_); + BlockingMutexLock lock(&mu_); uptr malloced = 0; Printf(" MallocInfo: in quarantine: %zu malloced: %zu; ", @@ -415,7 +323,7 @@ class MallocInfo { } PageGroup *FindPageGroup(uptr addr) { - ScopedLock lock(&mu_); + BlockingMutexLock lock(&mu_); return FindPageGroupUnlocked(addr); } @@ -462,14 +370,14 @@ class MallocInfo { return left_chunk; // Choose based on offset. uptr l_offset = 0, r_offset = 0; - CHECK(left_chunk->AddrIsAtRight(addr, 1, &l_offset)); - CHECK(right_chunk->AddrIsAtLeft(addr, 1, &r_offset)); + CHECK(AsanChunkView(left_chunk).AddrIsAtRight(addr, 1, &l_offset)); + CHECK(AsanChunkView(right_chunk).AddrIsAtLeft(addr, 1, &r_offset)); if (l_offset < r_offset) return left_chunk; return right_chunk; } - AsanChunk *FindChunkByAddr(uptr addr) { + AsanChunk *FindChunkByAddrUnlocked(uptr addr) { PageGroup *g = FindPageGroupUnlocked(addr); if (!g) return 0; CHECK(g->size_of_chunk); @@ -482,17 +390,18 @@ class MallocInfo { m->chunk_state == CHUNK_AVAILABLE || m->chunk_state == CHUNK_QUARANTINE); uptr offset = 0; - if (m->AddrIsInside(addr, 1, &offset)) + AsanChunkView m_view(m); + if (m_view.AddrIsInside(addr, 1, &offset)) return m; - if (m->AddrIsAtRight(addr, 1, &offset)) { + if (m_view.AddrIsAtRight(addr, 1, &offset)) { if (this_chunk_addr == g->last_chunk) // rightmost chunk return m; uptr right_chunk_addr = this_chunk_addr + g->size_of_chunk; CHECK(g->InRange(right_chunk_addr)); return ChooseChunk(addr, m, (AsanChunk*)right_chunk_addr); } else { - CHECK(m->AddrIsAtLeft(addr, 1, &offset)); + CHECK(m_view.AddrIsAtLeft(addr, 1, &offset)); if (this_chunk_addr == g->beg) // leftmost chunk return m; uptr left_chunk_addr = this_chunk_addr - g->size_of_chunk; @@ -533,12 +442,13 @@ class MallocInfo { uptr mmap_size = Max(size, kMinMmapSize); uptr n_chunks = mmap_size / size; CHECK(n_chunks * size == mmap_size); - if (size < kPageSize) { + uptr PageSize = GetPageSizeCached(); + if (size < PageSize) { // Size is small, just poison the last chunk. n_chunks--; } else { // Size is large, allocate an extra page at right and poison it. - mmap_size += kPageSize; + mmap_size += PageSize; } CHECK(n_chunks > 0); u8 *mem = MmapNewPagesAndPoisonShadow(mmap_size); @@ -564,14 +474,14 @@ class MallocInfo { pg->size_of_chunk = size; pg->last_chunk = (uptr)(mem + size * (n_chunks - 1)); int idx = atomic_fetch_add(&n_page_groups_, 1, memory_order_relaxed); - CHECK(idx < (int)ASAN_ARRAY_SIZE(page_groups_)); + CHECK(idx < (int)ARRAY_SIZE(page_groups_)); page_groups_[idx] = pg; return res; } AsanChunk *free_lists_[kNumberOfSizeClasses]; AsanChunkFifoList quarantine_; - AsanLock mu_; + BlockingMutex mu_; PageGroup *page_groups_[kMaxAvailableRam / kMinMmapSize]; atomic_uint32_t n_page_groups_; @@ -584,42 +494,12 @@ void AsanThreadLocalMallocStorage::CommitBack() { malloc_info.SwallowThreadLocalMallocStorage(this, true); } -static void Describe(uptr addr, uptr access_size) { - AsanChunk *m = malloc_info.FindMallocedOrFreed(addr, access_size); - if (!m) return; - m->DescribeAddress(addr, access_size); - CHECK(m->alloc_tid >= 0); - AsanThreadSummary *alloc_thread = - asanThreadRegistry().FindByTid(m->alloc_tid); - AsanStackTrace alloc_stack; - AsanStackTrace::UncompressStack(&alloc_stack, m->compressed_alloc_stack(), - m->compressed_alloc_stack_size()); - AsanThread *t = asanThreadRegistry().GetCurrent(); - CHECK(t); - if (m->free_tid != kInvalidTid) { - AsanThreadSummary *free_thread = - asanThreadRegistry().FindByTid(m->free_tid); - AsanPrintf("freed by thread T%d here:\n", free_thread->tid()); - AsanStackTrace free_stack; - AsanStackTrace::UncompressStack(&free_stack, m->compressed_free_stack(), - m->compressed_free_stack_size()); - free_stack.PrintStack(); - AsanPrintf("previously allocated by thread T%d here:\n", - alloc_thread->tid()); - - alloc_stack.PrintStack(); - t->summary()->Announce(); - free_thread->Announce(); - alloc_thread->Announce(); - } else { - AsanPrintf("allocated by thread T%d here:\n", alloc_thread->tid()); - alloc_stack.PrintStack(); - t->summary()->Announce(); - alloc_thread->Announce(); - } +AsanChunkView FindHeapChunkByAddress(uptr address) { + return AsanChunkView(malloc_info.FindChunkByAddr(address)); } -static u8 *Allocate(uptr alignment, uptr size, AsanStackTrace *stack) { +static u8 *Allocate(uptr alignment, uptr size, StackTrace *stack, + AllocType alloc_type) { __asan_init(); CHECK(stack); if (size == 0) { @@ -676,6 +556,7 @@ static u8 *Allocate(uptr alignment, uptr size, AsanStackTrace *stack) { CHECK(m); CHECK(m->chunk_state == CHUNK_AVAILABLE); m->chunk_state = CHUNK_ALLOCATED; + m->alloc_type = alloc_type; m->next = 0; CHECK(m->Size() == size_to_allocate); uptr addr = (uptr)m + REDZONE; @@ -697,7 +578,7 @@ static u8 *Allocate(uptr alignment, uptr size, AsanStackTrace *stack) { CHECK(m->Beg() == addr); m->alloc_tid = t ? t->tid() : 0; m->free_tid = kInvalidTid; - AsanStackTrace::CompressStack(stack, m->compressed_alloc_stack(), + StackTrace::CompressStack(stack, m->compressed_alloc_stack(), m->compressed_alloc_stack_size()); PoisonShadow(addr, rounded_size, 0); if (size < rounded_size) { @@ -710,7 +591,7 @@ static u8 *Allocate(uptr alignment, uptr size, AsanStackTrace *stack) { return (u8*)addr; } -static void Deallocate(u8 *ptr, AsanStackTrace *stack) { +static void Deallocate(u8 *ptr, StackTrace *stack, AllocType alloc_type) { if (!ptr) return; CHECK(stack); @@ -726,24 +607,21 @@ static void Deallocate(u8 *ptr, AsanStackTrace *stack) { memory_order_acq_rel); if (old_chunk_state == CHUNK_QUARANTINE) { - AsanReport("ERROR: AddressSanitizer attempting double-free on %p:\n", ptr); - stack->PrintStack(); - Describe((uptr)ptr, 1); - ShowStatsAndAbort(); + ReportDoubleFree((uptr)ptr, stack); } else if (old_chunk_state != CHUNK_ALLOCATED) { - AsanReport("ERROR: AddressSanitizer attempting free on address " - "which was not malloc()-ed: %p\n", ptr); - stack->PrintStack(); - ShowStatsAndAbort(); + ReportFreeNotMalloced((uptr)ptr, stack); } CHECK(old_chunk_state == CHUNK_ALLOCATED); + if (m->alloc_type != alloc_type && flags()->alloc_dealloc_mismatch) + ReportAllocTypeMismatch((uptr)ptr, stack, + (AllocType)m->alloc_type, (AllocType)alloc_type); // With REDZONE==16 m->next is in the user area, otherwise it should be 0. CHECK(REDZONE <= 16 || !m->next); CHECK(m->free_tid == kInvalidTid); CHECK(m->alloc_tid >= 0); AsanThread *t = asanThreadRegistry().GetCurrent(); m->free_tid = t ? t->tid() : 0; - AsanStackTrace::CompressStack(stack, m->compressed_free_stack(), + StackTrace::CompressStack(stack, m->compressed_free_stack(), m->compressed_free_stack_size()); uptr rounded_size = RoundUpTo(m->used_size, REDZONE); PoisonShadow((uptr)ptr, rounded_size, kAsanHeapFreeMagic); @@ -769,7 +647,7 @@ static void Deallocate(u8 *ptr, AsanStackTrace *stack) { } static u8 *Reallocate(u8 *old_ptr, uptr new_size, - AsanStackTrace *stack) { + StackTrace *stack) { CHECK(old_ptr && new_size); // Statistics. @@ -781,114 +659,112 @@ static u8 *Reallocate(u8 *old_ptr, uptr new_size, CHECK(m->chunk_state == CHUNK_ALLOCATED); uptr old_size = m->used_size; uptr memcpy_size = Min(new_size, old_size); - u8 *new_ptr = Allocate(0, new_size, stack); + u8 *new_ptr = Allocate(0, new_size, stack, FROM_MALLOC); if (new_ptr) { CHECK(REAL(memcpy) != 0); REAL(memcpy)(new_ptr, old_ptr, memcpy_size); - Deallocate(old_ptr, stack); + Deallocate(old_ptr, stack, FROM_MALLOC); } return new_ptr; } } // namespace __asan -// Malloc hooks declaration. -// ASAN_NEW_HOOK(ptr, size) is called immediately after -// allocation of "size" bytes, which returned "ptr". -// ASAN_DELETE_HOOK(ptr) is called immediately before -// deallocation of "ptr". -// If ASAN_NEW_HOOK or ASAN_DELETE_HOOK is defined, user -// program must provide implementation of this hook. -// If macro is undefined, the hook is no-op. -#ifdef ASAN_NEW_HOOK -extern "C" void ASAN_NEW_HOOK(void *ptr, uptr size); -#else -static inline void ASAN_NEW_HOOK(void *ptr, uptr size) { } -#endif - -#ifdef ASAN_DELETE_HOOK -extern "C" void ASAN_DELETE_HOOK(void *ptr); -#else -static inline void ASAN_DELETE_HOOK(void *ptr) { } +#if !SANITIZER_SUPPORTS_WEAK_HOOKS +// Provide default (no-op) implementation of malloc hooks. +extern "C" { +SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE +void __asan_malloc_hook(void *ptr, uptr size) { + (void)ptr; + (void)size; +} +SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE +void __asan_free_hook(void *ptr) { + (void)ptr; +} +} // extern "C" #endif namespace __asan { -void *asan_memalign(uptr alignment, uptr size, AsanStackTrace *stack) { - void *ptr = (void*)Allocate(alignment, size, stack); - ASAN_NEW_HOOK(ptr, size); +void PrintInternalAllocatorStats() { +} + +SANITIZER_INTERFACE_ATTRIBUTE +void *asan_memalign(uptr alignment, uptr size, StackTrace *stack, + AllocType alloc_type) { + void *ptr = (void*)Allocate(alignment, size, stack, alloc_type); + ASAN_MALLOC_HOOK(ptr, size); return ptr; } -void asan_free(void *ptr, AsanStackTrace *stack) { - ASAN_DELETE_HOOK(ptr); - Deallocate((u8*)ptr, stack); +SANITIZER_INTERFACE_ATTRIBUTE +void asan_free(void *ptr, StackTrace *stack, AllocType alloc_type) { + ASAN_FREE_HOOK(ptr); + Deallocate((u8*)ptr, stack, alloc_type); } -void *asan_malloc(uptr size, AsanStackTrace *stack) { - void *ptr = (void*)Allocate(0, size, stack); - ASAN_NEW_HOOK(ptr, size); +SANITIZER_INTERFACE_ATTRIBUTE +void *asan_malloc(uptr size, StackTrace *stack) { + void *ptr = (void*)Allocate(0, size, stack, FROM_MALLOC); + ASAN_MALLOC_HOOK(ptr, size); return ptr; } -void *asan_calloc(uptr nmemb, uptr size, AsanStackTrace *stack) { - void *ptr = (void*)Allocate(0, nmemb * size, stack); +void *asan_calloc(uptr nmemb, uptr size, StackTrace *stack) { + void *ptr = (void*)Allocate(0, nmemb * size, stack, FROM_MALLOC); if (ptr) REAL(memset)(ptr, 0, nmemb * size); - ASAN_NEW_HOOK(ptr, nmemb * size); + ASAN_MALLOC_HOOK(ptr, size); return ptr; } -void *asan_realloc(void *p, uptr size, AsanStackTrace *stack) { +void *asan_realloc(void *p, uptr size, StackTrace *stack) { if (p == 0) { - void *ptr = (void*)Allocate(0, size, stack); - ASAN_NEW_HOOK(ptr, size); + void *ptr = (void*)Allocate(0, size, stack, FROM_MALLOC); + ASAN_MALLOC_HOOK(ptr, size); return ptr; } else if (size == 0) { - ASAN_DELETE_HOOK(p); - Deallocate((u8*)p, stack); + ASAN_FREE_HOOK(p); + Deallocate((u8*)p, stack, FROM_MALLOC); return 0; } return Reallocate((u8*)p, size, stack); } -void *asan_valloc(uptr size, AsanStackTrace *stack) { - void *ptr = (void*)Allocate(kPageSize, size, stack); - ASAN_NEW_HOOK(ptr, size); +void *asan_valloc(uptr size, StackTrace *stack) { + void *ptr = (void*)Allocate(GetPageSizeCached(), size, stack, FROM_MALLOC); + ASAN_MALLOC_HOOK(ptr, size); return ptr; } -void *asan_pvalloc(uptr size, AsanStackTrace *stack) { - size = RoundUpTo(size, kPageSize); +void *asan_pvalloc(uptr size, StackTrace *stack) { + uptr PageSize = GetPageSizeCached(); + size = RoundUpTo(size, PageSize); if (size == 0) { // pvalloc(0) should allocate one page. - size = kPageSize; + size = PageSize; } - void *ptr = (void*)Allocate(kPageSize, size, stack); - ASAN_NEW_HOOK(ptr, size); + void *ptr = (void*)Allocate(PageSize, size, stack, FROM_MALLOC); + ASAN_MALLOC_HOOK(ptr, size); return ptr; } int asan_posix_memalign(void **memptr, uptr alignment, uptr size, - AsanStackTrace *stack) { - void *ptr = Allocate(alignment, size, stack); + StackTrace *stack) { + void *ptr = Allocate(alignment, size, stack, FROM_MALLOC); CHECK(IsAligned((uptr)ptr, alignment)); - ASAN_NEW_HOOK(ptr, size); + ASAN_MALLOC_HOOK(ptr, size); *memptr = ptr; return 0; } -uptr asan_malloc_usable_size(void *ptr, AsanStackTrace *stack) { +uptr asan_malloc_usable_size(void *ptr, StackTrace *stack) { CHECK(stack); if (ptr == 0) return 0; uptr usable_size = malloc_info.AllocationSize((uptr)ptr); if (flags()->check_malloc_usable_size && (usable_size == 0)) { - AsanReport("ERROR: AddressSanitizer attempting to call " - "malloc_usable_size() for pointer which is " - "not owned: %p\n", ptr); - stack->PrintStack(); - Describe((uptr)ptr, 1); - ShowStatsAndAbort(); + ReportMallocUsableSizeNotOwned((uptr)ptr, stack); } return usable_size; } @@ -897,10 +773,6 @@ uptr asan_mz_size(const void *ptr) { return malloc_info.AllocationSize((uptr)ptr); } -void DescribeHeapAddress(uptr addr, uptr access_size) { - Describe(addr, access_size); -} - void asan_mz_force_lock() { malloc_info.ForceLock(); } @@ -909,170 +781,11 @@ void asan_mz_force_unlock() { malloc_info.ForceUnlock(); } -// ---------------------- Fake stack-------------------- {{{1 -FakeStack::FakeStack() { - CHECK(REAL(memset) != 0); - REAL(memset)(this, 0, sizeof(*this)); -} - -bool FakeStack::AddrIsInSizeClass(uptr addr, uptr size_class) { - uptr mem = allocated_size_classes_[size_class]; - uptr size = ClassMmapSize(size_class); - bool res = mem && addr >= mem && addr < mem + size; - return res; -} - -uptr FakeStack::AddrIsInFakeStack(uptr addr) { - for (uptr i = 0; i < kNumberOfSizeClasses; i++) { - if (AddrIsInSizeClass(addr, i)) return allocated_size_classes_[i]; - } - return 0; -} - -// We may want to compute this during compilation. -inline uptr FakeStack::ComputeSizeClass(uptr alloc_size) { - uptr rounded_size = RoundUpToPowerOfTwo(alloc_size); - uptr log = Log2(rounded_size); - CHECK(alloc_size <= (1UL << log)); - if (!(alloc_size > (1UL << (log-1)))) { - Printf("alloc_size %zu log %zu\n", alloc_size, log); - } - CHECK(alloc_size > (1UL << (log-1))); - uptr res = log < kMinStackFrameSizeLog ? 0 : log - kMinStackFrameSizeLog; - CHECK(res < kNumberOfSizeClasses); - CHECK(ClassSize(res) >= rounded_size); - return res; -} - -void FakeFrameFifo::FifoPush(FakeFrame *node) { - CHECK(node); - node->next = 0; - if (first_ == 0 && last_ == 0) { - first_ = last_ = node; - } else { - CHECK(first_); - CHECK(last_); - last_->next = node; - last_ = node; - } -} - -FakeFrame *FakeFrameFifo::FifoPop() { - CHECK(first_ && last_ && "Exhausted fake stack"); - FakeFrame *res = 0; - if (first_ == last_) { - res = first_; - first_ = last_ = 0; - } else { - res = first_; - first_ = first_->next; - } - return res; -} - -void FakeStack::Init(uptr stack_size) { - stack_size_ = stack_size; - alive_ = true; -} - -void FakeStack::Cleanup() { - alive_ = false; - for (uptr i = 0; i < kNumberOfSizeClasses; i++) { - uptr mem = allocated_size_classes_[i]; - if (mem) { - PoisonShadow(mem, ClassMmapSize(i), 0); - allocated_size_classes_[i] = 0; - UnmapOrDie((void*)mem, ClassMmapSize(i)); - } - } -} - -uptr FakeStack::ClassMmapSize(uptr size_class) { - return RoundUpToPowerOfTwo(stack_size_); -} - -void FakeStack::AllocateOneSizeClass(uptr size_class) { - CHECK(ClassMmapSize(size_class) >= kPageSize); - uptr new_mem = (uptr)MmapOrDie( - ClassMmapSize(size_class), __FUNCTION__); - // Printf("T%d new_mem[%zu]: %p-%p mmap %zu\n", - // asanThreadRegistry().GetCurrent()->tid(), - // size_class, new_mem, new_mem + ClassMmapSize(size_class), - // ClassMmapSize(size_class)); - uptr i; - for (i = 0; i < ClassMmapSize(size_class); - i += ClassSize(size_class)) { - size_classes_[size_class].FifoPush((FakeFrame*)(new_mem + i)); - } - CHECK(i == ClassMmapSize(size_class)); - allocated_size_classes_[size_class] = new_mem; -} - -uptr FakeStack::AllocateStack(uptr size, uptr real_stack) { - if (!alive_) return real_stack; - CHECK(size <= kMaxStackMallocSize && size > 1); - uptr size_class = ComputeSizeClass(size); - if (!allocated_size_classes_[size_class]) { - AllocateOneSizeClass(size_class); - } - FakeFrame *fake_frame = size_classes_[size_class].FifoPop(); - CHECK(fake_frame); - fake_frame->size_minus_one = size - 1; - fake_frame->real_stack = real_stack; - while (FakeFrame *top = call_stack_.top()) { - if (top->real_stack > real_stack) break; - call_stack_.LifoPop(); - DeallocateFrame(top); - } - call_stack_.LifoPush(fake_frame); - uptr ptr = (uptr)fake_frame; - PoisonShadow(ptr, size, 0); - return ptr; -} - -void FakeStack::DeallocateFrame(FakeFrame *fake_frame) { - CHECK(alive_); - uptr size = fake_frame->size_minus_one + 1; - uptr size_class = ComputeSizeClass(size); - CHECK(allocated_size_classes_[size_class]); - uptr ptr = (uptr)fake_frame; - CHECK(AddrIsInSizeClass(ptr, size_class)); - CHECK(AddrIsInSizeClass(ptr + size - 1, size_class)); - size_classes_[size_class].FifoPush(fake_frame); -} - -void FakeStack::OnFree(uptr ptr, uptr size, uptr real_stack) { - FakeFrame *fake_frame = (FakeFrame*)ptr; - CHECK(fake_frame->magic = kRetiredStackFrameMagic); - CHECK(fake_frame->descr != 0); - CHECK(fake_frame->size_minus_one == size - 1); - PoisonShadow(ptr, size, kAsanStackAfterReturnMagic); -} - } // namespace __asan // ---------------------- Interface ---------------- {{{1 using namespace __asan; // NOLINT -uptr __asan_stack_malloc(uptr size, uptr real_stack) { - if (!flags()->use_fake_stack) return real_stack; - AsanThread *t = asanThreadRegistry().GetCurrent(); - if (!t) { - // TSD is gone, use the real stack. - return real_stack; - } - uptr ptr = t->fake_stack().AllocateStack(size, real_stack); - // Printf("__asan_stack_malloc %p %zu %p\n", ptr, size, real_stack); - return ptr; -} - -void __asan_stack_free(uptr ptr, uptr size, uptr real_stack) { - if (!flags()->use_fake_stack) return; - if (ptr != real_stack) { - FakeStack::OnFree(ptr, size, real_stack); - } -} - // ASan allocator doesn't reserve extra bytes, so normally we would // just return "size". uptr __asan_get_estimated_allocated_size(uptr size) { @@ -1089,12 +802,9 @@ uptr __asan_get_allocated_size(const void *p) { uptr allocated_size = malloc_info.AllocationSize((uptr)p); // Die if p is not malloced or if it is already freed. if (allocated_size == 0) { - AsanReport("ERROR: AddressSanitizer attempting to call " - "__asan_get_allocated_size() for pointer which is " - "not owned: %p\n", p); - PRINT_CURRENT_STACK(); - Describe((uptr)p, 1); - ShowStatsAndAbort(); + GET_STACK_TRACE_FATAL_HERE; + ReportAsanGetAllocatedSizeNotOwned((uptr)p, &stack); } return allocated_size; } +#endif // ASAN_ALLOCATOR_VERSION diff --git a/lib/asan/asan_allocator.h b/lib/asan/asan_allocator.h index 2aed59853868..cca24edad81f 100644 --- a/lib/asan/asan_allocator.h +++ b/lib/asan/asan_allocator.h @@ -17,13 +17,76 @@ #include "asan_internal.h" #include "asan_interceptors.h" +#include "sanitizer_common/sanitizer_list.h" + +// We are in the process of transitioning from the old allocator (version 1) +// to a new one (version 2). The change is quite intrusive so both allocators +// will co-exist in the source base for a while. The actual allocator is chosen +// at build time by redefining this macro. +#ifndef ASAN_ALLOCATOR_VERSION +# if ASAN_LINUX && !ASAN_ANDROID +# define ASAN_ALLOCATOR_VERSION 2 +# else +# define ASAN_ALLOCATOR_VERSION 1 +# endif +#endif // ASAN_ALLOCATOR_VERSION namespace __asan { +enum AllocType { + FROM_MALLOC = 1, // Memory block came from malloc, calloc, realloc, etc. + FROM_NEW = 2, // Memory block came from operator new. + FROM_NEW_BR = 3 // Memory block came from operator new [ ] +}; + static const uptr kNumberOfSizeClasses = 255; struct AsanChunk; -class AsanChunkFifoList { +class AsanChunkView { + public: + explicit AsanChunkView(AsanChunk *chunk) : chunk_(chunk) {} + bool IsValid() { return chunk_ != 0; } + uptr Beg(); // first byte of user memory. + uptr End(); // last byte of user memory. + uptr UsedSize(); // size requested by the user. + uptr AllocTid(); + uptr FreeTid(); + void GetAllocStack(StackTrace *stack); + void GetFreeStack(StackTrace *stack); + bool AddrIsInside(uptr addr, uptr access_size, uptr *offset) { + if (addr >= Beg() && (addr + access_size) <= End()) { + *offset = addr - Beg(); + return true; + } + return false; + } + bool AddrIsAtLeft(uptr addr, uptr access_size, uptr *offset) { + (void)access_size; + if (addr < Beg()) { + *offset = Beg() - addr; + return true; + } + return false; + } + bool AddrIsAtRight(uptr addr, uptr access_size, uptr *offset) { + if (addr + access_size >= End()) { + if (addr <= End()) + *offset = 0; + else + *offset = addr - End(); + return true; + } + return false; + } + + private: + AsanChunk *const chunk_; +}; + +AsanChunkView FindHeapChunkByAddress(uptr address); + +// List of AsanChunks with total size. +class AsanChunkFifoList: public IntrusiveList<AsanChunk> { public: explicit AsanChunkFifoList(LinkerInitialized) { } AsanChunkFifoList() { clear(); } @@ -32,25 +95,31 @@ class AsanChunkFifoList { AsanChunk *Pop(); uptr size() { return size_; } void clear() { - first_ = last_ = 0; + IntrusiveList<AsanChunk>::clear(); size_ = 0; } private: - AsanChunk *first_; - AsanChunk *last_; uptr size_; }; struct AsanThreadLocalMallocStorage { explicit AsanThreadLocalMallocStorage(LinkerInitialized x) - : quarantine_(x) { } +#if ASAN_ALLOCATOR_VERSION == 1 + : quarantine_(x) +#endif + { } AsanThreadLocalMallocStorage() { CHECK(REAL(memset)); REAL(memset)(this, 0, sizeof(AsanThreadLocalMallocStorage)); } +#if ASAN_ALLOCATOR_VERSION == 1 AsanChunkFifoList quarantine_; AsanChunk *free_lists_[kNumberOfSizeClasses]; +#else + uptr quarantine_cache[16]; + uptr allocator2_cache[96 * (512 * 8 + 16)]; // Opaque. +#endif void CommitBack(); }; @@ -108,6 +177,7 @@ class FakeStack { // Return the bottom of the maped region. uptr AddrIsInFakeStack(uptr addr); bool StackSize() { return stack_size_; } + private: static const uptr kMinStackFrameSizeLog = 9; // Min frame is 512B. static const uptr kMaxStackFrameSizeLog = 16; // Max stack frame is 64K. @@ -137,23 +207,70 @@ class FakeStack { FakeFrameLifo call_stack_; }; -void *asan_memalign(uptr alignment, uptr size, AsanStackTrace *stack); -void asan_free(void *ptr, AsanStackTrace *stack); +void *asan_memalign(uptr alignment, uptr size, StackTrace *stack, + AllocType alloc_type); +void asan_free(void *ptr, StackTrace *stack, AllocType alloc_type); -void *asan_malloc(uptr size, AsanStackTrace *stack); -void *asan_calloc(uptr nmemb, uptr size, AsanStackTrace *stack); -void *asan_realloc(void *p, uptr size, AsanStackTrace *stack); -void *asan_valloc(uptr size, AsanStackTrace *stack); -void *asan_pvalloc(uptr size, AsanStackTrace *stack); +void *asan_malloc(uptr size, StackTrace *stack); +void *asan_calloc(uptr nmemb, uptr size, StackTrace *stack); +void *asan_realloc(void *p, uptr size, StackTrace *stack); +void *asan_valloc(uptr size, StackTrace *stack); +void *asan_pvalloc(uptr size, StackTrace *stack); int asan_posix_memalign(void **memptr, uptr alignment, uptr size, - AsanStackTrace *stack); -uptr asan_malloc_usable_size(void *ptr, AsanStackTrace *stack); + StackTrace *stack); +uptr asan_malloc_usable_size(void *ptr, StackTrace *stack); uptr asan_mz_size(const void *ptr); void asan_mz_force_lock(); void asan_mz_force_unlock(); -void DescribeHeapAddress(uptr addr, uptr access_size); + +void PrintInternalAllocatorStats(); + +// Log2 and RoundUpToPowerOfTwo should be inlined for performance. +#if defined(_WIN32) && !defined(__clang__) +extern "C" { +unsigned char _BitScanForward(unsigned long *index, unsigned long mask); // NOLINT +unsigned char _BitScanReverse(unsigned long *index, unsigned long mask); // NOLINT +#if defined(_WIN64) +unsigned char _BitScanForward64(unsigned long *index, unsigned __int64 mask); // NOLINT +unsigned char _BitScanReverse64(unsigned long *index, unsigned __int64 mask); // NOLINT +#endif +} +#endif + +static inline uptr Log2(uptr x) { + CHECK(IsPowerOfTwo(x)); +#if !defined(_WIN32) || defined(__clang__) + return __builtin_ctzl(x); +#elif defined(_WIN64) + unsigned long ret; // NOLINT + _BitScanForward64(&ret, x); + return ret; +#else + unsigned long ret; // NOLINT + _BitScanForward(&ret, x); + return ret; +#endif +} + +static inline uptr RoundUpToPowerOfTwo(uptr size) { + CHECK(size); + if (IsPowerOfTwo(size)) return size; + + unsigned long up; // NOLINT +#if !defined(_WIN32) || defined(__clang__) + up = SANITIZER_WORDSIZE - 1 - __builtin_clzl(size); +#elif defined(_WIN64) + _BitScanReverse64(&up, size); +#else + _BitScanReverse(&up, size); +#endif + CHECK(size < (1ULL << (up + 1))); + CHECK(size > (1ULL << up)); + return 1UL << (up + 1); +} + } // namespace __asan #endif // ASAN_ALLOCATOR_H diff --git a/lib/asan/asan_allocator2.cc b/lib/asan/asan_allocator2.cc new file mode 100644 index 000000000000..42d8b29afd6b --- /dev/null +++ b/lib/asan/asan_allocator2.cc @@ -0,0 +1,710 @@ +//===-- asan_allocator2.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. +// +// Implementation of ASan's memory allocator, 2-nd version. +// This variant uses the allocator from sanitizer_common, i.e. the one shared +// with ThreadSanitizer and MemorySanitizer. +// +// Status: under development, not enabled by default yet. +//===----------------------------------------------------------------------===// +#include "asan_allocator.h" +#if ASAN_ALLOCATOR_VERSION == 2 + +#include "asan_mapping.h" +#include "asan_report.h" +#include "asan_thread.h" +#include "asan_thread_registry.h" +#include "sanitizer/asan_interface.h" +#include "sanitizer_common/sanitizer_allocator.h" +#include "sanitizer_common/sanitizer_internal_defs.h" +#include "sanitizer_common/sanitizer_list.h" +#include "sanitizer_common/sanitizer_stackdepot.h" +#include "sanitizer_common/sanitizer_quarantine.h" + +namespace __asan { + +struct AsanMapUnmapCallback { + void OnMap(uptr p, uptr size) const { + PoisonShadow(p, size, kAsanHeapLeftRedzoneMagic); + // Statistics. + AsanStats &thread_stats = asanThreadRegistry().GetCurrentThreadStats(); + thread_stats.mmaps++; + thread_stats.mmaped += size; + } + void OnUnmap(uptr p, uptr size) const { + PoisonShadow(p, size, 0); + // We are about to unmap a chunk of user memory. + // Mark the corresponding shadow memory as not needed. + // Since asan's mapping is compacting, the shadow chunk may be + // not page-aligned, so we only flush the page-aligned portion. + uptr page_size = GetPageSizeCached(); + uptr shadow_beg = RoundUpTo(MemToShadow(p), page_size); + uptr shadow_end = RoundDownTo(MemToShadow(p + size), page_size); + FlushUnneededShadowMemory(shadow_beg, shadow_end - shadow_beg); + // Statistics. + AsanStats &thread_stats = asanThreadRegistry().GetCurrentThreadStats(); + thread_stats.munmaps++; + thread_stats.munmaped += size; + } +}; + +#if SANITIZER_WORDSIZE == 64 +const uptr kAllocatorSpace = 0x600000000000ULL; +const uptr kAllocatorSize = 0x10000000000ULL; // 1T. +typedef DefaultSizeClassMap SizeClassMap; +typedef SizeClassAllocator64<kAllocatorSpace, kAllocatorSize, 0 /*metadata*/, + SizeClassMap, AsanMapUnmapCallback> PrimaryAllocator; +#elif SANITIZER_WORDSIZE == 32 +static const u64 kAddressSpaceSize = 1ULL << 32; +typedef CompactSizeClassMap SizeClassMap; +typedef SizeClassAllocator32<0, kAddressSpaceSize, 16, + SizeClassMap, AsanMapUnmapCallback> PrimaryAllocator; +#endif + +typedef SizeClassAllocatorLocalCache<PrimaryAllocator> AllocatorCache; +typedef LargeMmapAllocator<AsanMapUnmapCallback> SecondaryAllocator; +typedef CombinedAllocator<PrimaryAllocator, AllocatorCache, + SecondaryAllocator> Allocator; + +// We can not use THREADLOCAL because it is not supported on some of the +// platforms we care about (OSX 10.6, Android). +// static THREADLOCAL AllocatorCache cache; +AllocatorCache *GetAllocatorCache(AsanThreadLocalMallocStorage *ms) { + CHECK(ms); + CHECK_LE(sizeof(AllocatorCache), sizeof(ms->allocator2_cache)); + return reinterpret_cast<AllocatorCache *>(ms->allocator2_cache); +} + +static Allocator allocator; + +static const uptr kMaxAllowedMallocSize = + FIRST_32_SECOND_64(3UL << 30, 8UL << 30); + +static const uptr kMaxThreadLocalQuarantine = + FIRST_32_SECOND_64(1 << 18, 1 << 20); + +static const uptr kReturnOnZeroMalloc = 2048; // Zero page is protected. + +// Every chunk of memory allocated by this allocator can be in one of 3 states: +// CHUNK_AVAILABLE: the chunk is in the free list and ready to be allocated. +// CHUNK_ALLOCATED: the chunk is allocated and not yet freed. +// CHUNK_QUARANTINE: the chunk was freed and put into quarantine zone. +enum { + CHUNK_AVAILABLE = 0, // 0 is the default value even if we didn't set it. + CHUNK_ALLOCATED = 2, + CHUNK_QUARANTINE = 3 +}; + +// Valid redzone sizes are 16, 32, 64, ... 2048, so we encode them in 3 bits. +// We use adaptive redzones: for larger allocation larger redzones are used. +static u32 RZLog2Size(u32 rz_log) { + CHECK_LT(rz_log, 8); + return 16 << rz_log; +} + +static u32 RZSize2Log(u32 rz_size) { + CHECK_GE(rz_size, 16); + CHECK_LE(rz_size, 2048); + CHECK(IsPowerOfTwo(rz_size)); + u32 res = __builtin_ctz(rz_size) - 4; + CHECK_EQ(rz_size, RZLog2Size(res)); + return res; +} + +static uptr ComputeRZLog(uptr user_requested_size) { + u32 rz_log = + user_requested_size <= 64 - 16 ? 0 : + user_requested_size <= 128 - 32 ? 1 : + user_requested_size <= 512 - 64 ? 2 : + user_requested_size <= 4096 - 128 ? 3 : + user_requested_size <= (1 << 14) - 256 ? 4 : + user_requested_size <= (1 << 15) - 512 ? 5 : + user_requested_size <= (1 << 16) - 1024 ? 6 : 7; + return Max(rz_log, RZSize2Log(flags()->redzone)); +} + +// The memory chunk allocated from the underlying allocator looks like this: +// L L L L L L H H U U U U U U R R +// L -- left redzone words (0 or more bytes) +// H -- ChunkHeader (16 bytes), which is also a part of the left redzone. +// U -- user memory. +// R -- right redzone (0 or more bytes) +// ChunkBase consists of ChunkHeader and other bytes that overlap with user +// memory. + +// If a memory chunk is allocated by memalign and we had to increase the +// allocation size to achieve the proper alignment, then we store this magic +// value in the first uptr word of the memory block and store the address of +// ChunkBase in the next uptr. +// M B ? ? ? L L L L L L H H U U U U U U +// M -- magic value kMemalignMagic +// B -- address of ChunkHeader pointing to the first 'H' +static const uptr kMemalignMagic = 0xCC6E96B9; + +struct ChunkHeader { + // 1-st 8 bytes. + u32 chunk_state : 8; // Must be first. + u32 alloc_tid : 24; + + u32 free_tid : 24; + u32 from_memalign : 1; + u32 alloc_type : 2; + u32 rz_log : 3; + // 2-nd 8 bytes + // This field is used for small sizes. For large sizes it is equal to + // SizeClassMap::kMaxSize and the actual size is stored in the + // SecondaryAllocator's metadata. + u32 user_requested_size; + u32 alloc_context_id; +}; + +struct ChunkBase : ChunkHeader { + // Header2, intersects with user memory. + AsanChunk *next; + u32 free_context_id; +}; + +static const uptr kChunkHeaderSize = sizeof(ChunkHeader); +static const uptr kChunkHeader2Size = sizeof(ChunkBase) - kChunkHeaderSize; +COMPILER_CHECK(kChunkHeaderSize == 16); +COMPILER_CHECK(kChunkHeader2Size <= 16); + +struct AsanChunk: ChunkBase { + uptr Beg() { return reinterpret_cast<uptr>(this) + kChunkHeaderSize; } + uptr UsedSize() { + if (user_requested_size != SizeClassMap::kMaxSize) + return user_requested_size; + return *reinterpret_cast<uptr *>(allocator.GetMetaData(AllocBeg())); + } + void *AllocBeg() { + if (from_memalign) + return allocator.GetBlockBegin(reinterpret_cast<void *>(this)); + return reinterpret_cast<void*>(Beg() - RZLog2Size(rz_log)); + } + // We store the alloc/free stack traces in the chunk itself. + u32 *AllocStackBeg() { + return (u32*)(Beg() - RZLog2Size(rz_log)); + } + uptr AllocStackSize() { + CHECK_LE(RZLog2Size(rz_log), kChunkHeaderSize); + return (RZLog2Size(rz_log) - kChunkHeaderSize) / sizeof(u32); + } + u32 *FreeStackBeg() { + return (u32*)(Beg() + kChunkHeader2Size); + } + uptr FreeStackSize() { + if (user_requested_size < kChunkHeader2Size) return 0; + uptr available = RoundUpTo(user_requested_size, SHADOW_GRANULARITY); + return (available - kChunkHeader2Size) / sizeof(u32); + } +}; + +uptr AsanChunkView::Beg() { return chunk_->Beg(); } +uptr AsanChunkView::End() { return Beg() + UsedSize(); } +uptr AsanChunkView::UsedSize() { return chunk_->UsedSize(); } +uptr AsanChunkView::AllocTid() { return chunk_->alloc_tid; } +uptr AsanChunkView::FreeTid() { return chunk_->free_tid; } + +static void GetStackTraceFromId(u32 id, StackTrace *stack) { + CHECK(id); + uptr size = 0; + const uptr *trace = StackDepotGet(id, &size); + CHECK_LT(size, kStackTraceMax); + internal_memcpy(stack->trace, trace, sizeof(uptr) * size); + stack->size = size; +} + +void AsanChunkView::GetAllocStack(StackTrace *stack) { + if (flags()->use_stack_depot) + GetStackTraceFromId(chunk_->alloc_context_id, stack); + else + StackTrace::UncompressStack(stack, chunk_->AllocStackBeg(), + chunk_->AllocStackSize()); +} + +void AsanChunkView::GetFreeStack(StackTrace *stack) { + if (flags()->use_stack_depot) + GetStackTraceFromId(chunk_->free_context_id, stack); + else + StackTrace::UncompressStack(stack, chunk_->FreeStackBeg(), + chunk_->FreeStackSize()); +} + +struct QuarantineCallback; +typedef Quarantine<QuarantineCallback, AsanChunk> AsanQuarantine; +typedef AsanQuarantine::Cache QuarantineCache; +static AsanQuarantine quarantine(LINKER_INITIALIZED); +static QuarantineCache fallback_quarantine_cache(LINKER_INITIALIZED); +static AllocatorCache fallback_allocator_cache; +static SpinMutex fallback_mutex; + +QuarantineCache *GetQuarantineCache(AsanThreadLocalMallocStorage *ms) { + CHECK(ms); + CHECK_LE(sizeof(QuarantineCache), sizeof(ms->quarantine_cache)); + return reinterpret_cast<QuarantineCache *>(ms->quarantine_cache); +} + +struct QuarantineCallback { + explicit QuarantineCallback(AllocatorCache *cache) + : cache_(cache) { + } + + void Recycle(AsanChunk *m) { + CHECK(m->chunk_state == CHUNK_QUARANTINE); + m->chunk_state = CHUNK_AVAILABLE; + CHECK_NE(m->alloc_tid, kInvalidTid); + CHECK_NE(m->free_tid, kInvalidTid); + PoisonShadow(m->Beg(), + RoundUpTo(m->UsedSize(), SHADOW_GRANULARITY), + kAsanHeapLeftRedzoneMagic); + void *p = reinterpret_cast<void *>(m->AllocBeg()); + if (m->from_memalign) { + uptr *memalign_magic = reinterpret_cast<uptr *>(p); + CHECK_EQ(memalign_magic[0], kMemalignMagic); + CHECK_EQ(memalign_magic[1], reinterpret_cast<uptr>(m)); + } + + // Statistics. + AsanStats &thread_stats = asanThreadRegistry().GetCurrentThreadStats(); + thread_stats.real_frees++; + thread_stats.really_freed += m->UsedSize(); + + allocator.Deallocate(cache_, p); + } + + void *Allocate(uptr size) { + return allocator.Allocate(cache_, size, 1, false); + } + + void Deallocate(void *p) { + allocator.Deallocate(cache_, p); + } + + AllocatorCache *cache_; +}; + +static void Init() { + static int inited = 0; + if (inited) return; + __asan_init(); + inited = true; // this must happen before any threads are created. + allocator.Init(); + quarantine.Init((uptr)flags()->quarantine_size, kMaxThreadLocalQuarantine); +} + +static void *Allocate(uptr size, uptr alignment, StackTrace *stack, + AllocType alloc_type) { + Init(); + CHECK(stack); + const uptr min_alignment = SHADOW_GRANULARITY; + if (alignment < min_alignment) + alignment = min_alignment; + if (size == 0) { + if (alignment <= kReturnOnZeroMalloc) + return reinterpret_cast<void *>(kReturnOnZeroMalloc); + else + return 0; // 0 bytes with large alignment requested. Just return 0. + } + CHECK(IsPowerOfTwo(alignment)); + uptr rz_log = ComputeRZLog(size); + uptr rz_size = RZLog2Size(rz_log); + uptr rounded_size = RoundUpTo(size, alignment); + if (rounded_size < kChunkHeader2Size) + rounded_size = kChunkHeader2Size; + uptr needed_size = rounded_size + rz_size; + if (alignment > min_alignment) + needed_size += alignment; + bool using_primary_allocator = true; + // If we are allocating from the secondary allocator, there will be no + // automatic right redzone, so add the right redzone manually. + if (!PrimaryAllocator::CanAllocate(needed_size, alignment)) { + needed_size += rz_size; + using_primary_allocator = false; + } + CHECK(IsAligned(needed_size, min_alignment)); + if (size > kMaxAllowedMallocSize || needed_size > kMaxAllowedMallocSize) { + Report("WARNING: AddressSanitizer failed to allocate %p bytes\n", + (void*)size); + return 0; + } + + AsanThread *t = asanThreadRegistry().GetCurrent(); + void *allocated; + if (t) { + AllocatorCache *cache = GetAllocatorCache(&t->malloc_storage()); + allocated = allocator.Allocate(cache, needed_size, 8, false); + } else { + SpinMutexLock l(&fallback_mutex); + AllocatorCache *cache = &fallback_allocator_cache; + allocated = allocator.Allocate(cache, needed_size, 8, false); + } + uptr alloc_beg = reinterpret_cast<uptr>(allocated); + // Clear the first allocated word (an old kMemalignMagic may still be there). + reinterpret_cast<uptr *>(alloc_beg)[0] = 0; + uptr alloc_end = alloc_beg + needed_size; + uptr beg_plus_redzone = alloc_beg + rz_size; + uptr user_beg = beg_plus_redzone; + if (!IsAligned(user_beg, alignment)) + user_beg = RoundUpTo(user_beg, alignment); + uptr user_end = user_beg + size; + CHECK_LE(user_end, alloc_end); + uptr chunk_beg = user_beg - kChunkHeaderSize; + AsanChunk *m = reinterpret_cast<AsanChunk *>(chunk_beg); + m->chunk_state = CHUNK_ALLOCATED; + m->alloc_type = alloc_type; + m->rz_log = rz_log; + u32 alloc_tid = t ? t->tid() : 0; + m->alloc_tid = alloc_tid; + CHECK_EQ(alloc_tid, m->alloc_tid); // Does alloc_tid fit into the bitfield? + m->free_tid = kInvalidTid; + m->from_memalign = user_beg != beg_plus_redzone; + if (m->from_memalign) { + CHECK_LE(beg_plus_redzone + 2 * sizeof(uptr), user_beg); + uptr *memalign_magic = reinterpret_cast<uptr *>(alloc_beg); + memalign_magic[0] = kMemalignMagic; + memalign_magic[1] = chunk_beg; + } + if (using_primary_allocator) { + CHECK(size); + m->user_requested_size = size; + CHECK(allocator.FromPrimary(allocated)); + } else { + CHECK(!allocator.FromPrimary(allocated)); + m->user_requested_size = SizeClassMap::kMaxSize; + uptr *meta = reinterpret_cast<uptr *>(allocator.GetMetaData(allocated)); + meta[0] = size; + meta[1] = chunk_beg; + } + + if (flags()->use_stack_depot) { + m->alloc_context_id = StackDepotPut(stack->trace, stack->size); + } else { + m->alloc_context_id = 0; + StackTrace::CompressStack(stack, m->AllocStackBeg(), m->AllocStackSize()); + } + + uptr size_rounded_down_to_granularity = RoundDownTo(size, SHADOW_GRANULARITY); + // Unpoison the bulk of the memory region. + if (size_rounded_down_to_granularity) + PoisonShadow(user_beg, size_rounded_down_to_granularity, 0); + // Deal with the end of the region if size is not aligned to granularity. + if (size != size_rounded_down_to_granularity && flags()->poison_heap) { + u8 *shadow = (u8*)MemToShadow(user_beg + size_rounded_down_to_granularity); + *shadow = size & (SHADOW_GRANULARITY - 1); + } + + AsanStats &thread_stats = asanThreadRegistry().GetCurrentThreadStats(); + thread_stats.mallocs++; + thread_stats.malloced += size; + thread_stats.malloced_redzones += needed_size - size; + uptr class_id = Min(kNumberOfSizeClasses, SizeClassMap::ClassID(needed_size)); + thread_stats.malloced_by_size[class_id]++; + if (needed_size > SizeClassMap::kMaxSize) + thread_stats.malloc_large++; + + void *res = reinterpret_cast<void *>(user_beg); + ASAN_MALLOC_HOOK(res, size); + return res; +} + +static void Deallocate(void *ptr, StackTrace *stack, AllocType alloc_type) { + uptr p = reinterpret_cast<uptr>(ptr); + if (p == 0 || p == kReturnOnZeroMalloc) return; + uptr chunk_beg = p - kChunkHeaderSize; + AsanChunk *m = reinterpret_cast<AsanChunk *>(chunk_beg); + + // Flip the chunk_state atomically to avoid race on double-free. + u8 old_chunk_state = atomic_exchange((atomic_uint8_t*)m, CHUNK_QUARANTINE, + memory_order_relaxed); + + if (old_chunk_state == CHUNK_QUARANTINE) + ReportDoubleFree((uptr)ptr, stack); + else if (old_chunk_state != CHUNK_ALLOCATED) + ReportFreeNotMalloced((uptr)ptr, stack); + CHECK(old_chunk_state == CHUNK_ALLOCATED); + if (m->alloc_type != alloc_type && flags()->alloc_dealloc_mismatch) + ReportAllocTypeMismatch((uptr)ptr, stack, + (AllocType)m->alloc_type, (AllocType)alloc_type); + + CHECK_GE(m->alloc_tid, 0); + if (SANITIZER_WORDSIZE == 64) // On 32-bits this resides in user area. + CHECK_EQ(m->free_tid, kInvalidTid); + AsanThread *t = asanThreadRegistry().GetCurrent(); + m->free_tid = t ? t->tid() : 0; + if (flags()->use_stack_depot) { + m->free_context_id = StackDepotPut(stack->trace, stack->size); + } else { + m->free_context_id = 0; + StackTrace::CompressStack(stack, m->FreeStackBeg(), m->FreeStackSize()); + } + CHECK(m->chunk_state == CHUNK_QUARANTINE); + // Poison the region. + PoisonShadow(m->Beg(), + RoundUpTo(m->UsedSize(), SHADOW_GRANULARITY), + kAsanHeapFreeMagic); + + AsanStats &thread_stats = asanThreadRegistry().GetCurrentThreadStats(); + thread_stats.frees++; + thread_stats.freed += m->UsedSize(); + + // Push into quarantine. + if (t) { + AsanThreadLocalMallocStorage *ms = &t->malloc_storage(); + AllocatorCache *ac = GetAllocatorCache(ms); + quarantine.Put(GetQuarantineCache(ms), QuarantineCallback(ac), + m, m->UsedSize()); + } else { + SpinMutexLock l(&fallback_mutex); + AllocatorCache *ac = &fallback_allocator_cache; + quarantine.Put(&fallback_quarantine_cache, QuarantineCallback(ac), + m, m->UsedSize()); + } + + ASAN_FREE_HOOK(ptr); +} + +static void *Reallocate(void *old_ptr, uptr new_size, StackTrace *stack) { + CHECK(old_ptr && new_size); + uptr p = reinterpret_cast<uptr>(old_ptr); + uptr chunk_beg = p - kChunkHeaderSize; + AsanChunk *m = reinterpret_cast<AsanChunk *>(chunk_beg); + + AsanStats &thread_stats = asanThreadRegistry().GetCurrentThreadStats(); + thread_stats.reallocs++; + thread_stats.realloced += new_size; + + CHECK(m->chunk_state == CHUNK_ALLOCATED); + uptr old_size = m->UsedSize(); + uptr memcpy_size = Min(new_size, old_size); + void *new_ptr = Allocate(new_size, 8, stack, FROM_MALLOC); + if (new_ptr) { + CHECK(REAL(memcpy) != 0); + REAL(memcpy)(new_ptr, old_ptr, memcpy_size); + Deallocate(old_ptr, stack, FROM_MALLOC); + } + return new_ptr; +} + +static AsanChunk *GetAsanChunkByAddr(uptr p) { + void *ptr = reinterpret_cast<void *>(p); + uptr alloc_beg = reinterpret_cast<uptr>(allocator.GetBlockBegin(ptr)); + if (!alloc_beg) return 0; + uptr *memalign_magic = reinterpret_cast<uptr *>(alloc_beg); + if (memalign_magic[0] == kMemalignMagic) { + AsanChunk *m = reinterpret_cast<AsanChunk *>(memalign_magic[1]); + CHECK(m->from_memalign); + return m; + } + if (!allocator.FromPrimary(ptr)) { + uptr *meta = reinterpret_cast<uptr *>( + allocator.GetMetaData(reinterpret_cast<void *>(alloc_beg))); + AsanChunk *m = reinterpret_cast<AsanChunk *>(meta[1]); + return m; + } + uptr actual_size = allocator.GetActuallyAllocatedSize(ptr); + CHECK_LE(actual_size, SizeClassMap::kMaxSize); + // We know the actually allocted size, but we don't know the redzone size. + // Just try all possible redzone sizes. + for (u32 rz_log = 0; rz_log < 8; rz_log++) { + u32 rz_size = RZLog2Size(rz_log); + uptr max_possible_size = actual_size - rz_size; + if (ComputeRZLog(max_possible_size) != rz_log) + continue; + return reinterpret_cast<AsanChunk *>( + alloc_beg + rz_size - kChunkHeaderSize); + } + return 0; +} + +static uptr AllocationSize(uptr p) { + AsanChunk *m = GetAsanChunkByAddr(p); + if (!m) return 0; + if (m->chunk_state != CHUNK_ALLOCATED) return 0; + if (m->Beg() != p) return 0; + return m->UsedSize(); +} + +// We have an address between two chunks, and we want to report just one. +AsanChunk *ChooseChunk(uptr addr, + AsanChunk *left_chunk, AsanChunk *right_chunk) { + // Prefer an allocated chunk over freed chunk and freed chunk + // over available chunk. + if (left_chunk->chunk_state != right_chunk->chunk_state) { + if (left_chunk->chunk_state == CHUNK_ALLOCATED) + return left_chunk; + if (right_chunk->chunk_state == CHUNK_ALLOCATED) + return right_chunk; + if (left_chunk->chunk_state == CHUNK_QUARANTINE) + return left_chunk; + if (right_chunk->chunk_state == CHUNK_QUARANTINE) + return right_chunk; + } + // Same chunk_state: choose based on offset. + uptr l_offset = 0, r_offset = 0; + CHECK(AsanChunkView(left_chunk).AddrIsAtRight(addr, 1, &l_offset)); + CHECK(AsanChunkView(right_chunk).AddrIsAtLeft(addr, 1, &r_offset)); + if (l_offset < r_offset) + return left_chunk; + return right_chunk; +} + +AsanChunkView FindHeapChunkByAddress(uptr addr) { + AsanChunk *m1 = GetAsanChunkByAddr(addr); + if (!m1) return AsanChunkView(m1); + uptr offset = 0; + if (AsanChunkView(m1).AddrIsAtLeft(addr, 1, &offset)) { + // The address is in the chunk's left redzone, so maybe it is actually + // a right buffer overflow from the other chunk to the left. + // Search a bit to the left to see if there is another chunk. + AsanChunk *m2 = 0; + for (uptr l = 1; l < GetPageSizeCached(); l++) { + m2 = GetAsanChunkByAddr(addr - l); + if (m2 == m1) continue; // Still the same chunk. + break; + } + if (m2 && AsanChunkView(m2).AddrIsAtRight(addr, 1, &offset)) + m1 = ChooseChunk(addr, m2, m1); + } + return AsanChunkView(m1); +} + +void AsanThreadLocalMallocStorage::CommitBack() { + AllocatorCache *ac = GetAllocatorCache(this); + quarantine.Drain(GetQuarantineCache(this), QuarantineCallback(ac)); + allocator.SwallowCache(GetAllocatorCache(this)); +} + +void PrintInternalAllocatorStats() { + allocator.PrintStats(); +} + +SANITIZER_INTERFACE_ATTRIBUTE +void *asan_memalign(uptr alignment, uptr size, StackTrace *stack, + AllocType alloc_type) { + return Allocate(size, alignment, stack, alloc_type); +} + +SANITIZER_INTERFACE_ATTRIBUTE +void asan_free(void *ptr, StackTrace *stack, AllocType alloc_type) { + Deallocate(ptr, stack, alloc_type); +} + +SANITIZER_INTERFACE_ATTRIBUTE +void *asan_malloc(uptr size, StackTrace *stack) { + return Allocate(size, 8, stack, FROM_MALLOC); +} + +void *asan_calloc(uptr nmemb, uptr size, StackTrace *stack) { + void *ptr = Allocate(nmemb * size, 8, stack, FROM_MALLOC); + if (ptr) + REAL(memset)(ptr, 0, nmemb * size); + return ptr; +} + +void *asan_realloc(void *p, uptr size, StackTrace *stack) { + if (p == 0) + return Allocate(size, 8, stack, FROM_MALLOC); + if (size == 0) { + Deallocate(p, stack, FROM_MALLOC); + return 0; + } + return Reallocate(p, size, stack); +} + +void *asan_valloc(uptr size, StackTrace *stack) { + return Allocate(size, GetPageSizeCached(), stack, FROM_MALLOC); +} + +void *asan_pvalloc(uptr size, StackTrace *stack) { + uptr PageSize = GetPageSizeCached(); + size = RoundUpTo(size, PageSize); + if (size == 0) { + // pvalloc(0) should allocate one page. + size = PageSize; + } + return Allocate(size, PageSize, stack, FROM_MALLOC); +} + +int asan_posix_memalign(void **memptr, uptr alignment, uptr size, + StackTrace *stack) { + void *ptr = Allocate(size, alignment, stack, FROM_MALLOC); + CHECK(IsAligned((uptr)ptr, alignment)); + *memptr = ptr; + return 0; +} + +uptr asan_malloc_usable_size(void *ptr, StackTrace *stack) { + CHECK(stack); + if (ptr == 0) return 0; + uptr usable_size = AllocationSize(reinterpret_cast<uptr>(ptr)); + if (flags()->check_malloc_usable_size && (usable_size == 0)) + ReportMallocUsableSizeNotOwned((uptr)ptr, stack); + return usable_size; +} + +uptr asan_mz_size(const void *ptr) { + UNIMPLEMENTED(); + return 0; +} + +void asan_mz_force_lock() { + UNIMPLEMENTED(); +} + +void asan_mz_force_unlock() { + UNIMPLEMENTED(); +} + +} // namespace __asan + +// ---------------------- Interface ---------------- {{{1 +using namespace __asan; // NOLINT + +// ASan allocator doesn't reserve extra bytes, so normally we would +// just return "size". We don't want to expose our redzone sizes, etc here. +uptr __asan_get_estimated_allocated_size(uptr size) { + return size; +} + +bool __asan_get_ownership(const void *p) { + uptr ptr = reinterpret_cast<uptr>(p); + return (ptr == kReturnOnZeroMalloc) || (AllocationSize(ptr) > 0); +} + +uptr __asan_get_allocated_size(const void *p) { + if (p == 0) return 0; + uptr ptr = reinterpret_cast<uptr>(p); + uptr allocated_size = AllocationSize(ptr); + // Die if p is not malloced or if it is already freed. + if (allocated_size == 0 && ptr != kReturnOnZeroMalloc) { + GET_STACK_TRACE_FATAL_HERE; + ReportAsanGetAllocatedSizeNotOwned(ptr, &stack); + } + return allocated_size; +} + +#if !SANITIZER_SUPPORTS_WEAK_HOOKS +// Provide default (no-op) implementation of malloc hooks. +extern "C" { +SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE +void __asan_malloc_hook(void *ptr, uptr size) { + (void)ptr; + (void)size; +} +SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE +void __asan_free_hook(void *ptr) { + (void)ptr; +} +} // extern "C" +#endif + + +#endif // ASAN_ALLOCATOR_VERSION diff --git a/lib/asan/asan_fake_stack.cc b/lib/asan/asan_fake_stack.cc new file mode 100644 index 000000000000..7c5a16312d46 --- /dev/null +++ b/lib/asan/asan_fake_stack.cc @@ -0,0 +1,182 @@ +//===-- asan_fake_stack.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. +// +// FakeStack is used to detect use-after-return bugs. +//===----------------------------------------------------------------------===// +#include "asan_allocator.h" +#include "asan_thread.h" +#include "asan_thread_registry.h" +#include "sanitizer/asan_interface.h" + +namespace __asan { + +FakeStack::FakeStack() { + CHECK(REAL(memset) != 0); + REAL(memset)(this, 0, sizeof(*this)); +} + +bool FakeStack::AddrIsInSizeClass(uptr addr, uptr size_class) { + uptr mem = allocated_size_classes_[size_class]; + uptr size = ClassMmapSize(size_class); + bool res = mem && addr >= mem && addr < mem + size; + return res; +} + +uptr FakeStack::AddrIsInFakeStack(uptr addr) { + for (uptr i = 0; i < kNumberOfSizeClasses; i++) { + if (AddrIsInSizeClass(addr, i)) return allocated_size_classes_[i]; + } + return 0; +} + +// We may want to compute this during compilation. +inline uptr FakeStack::ComputeSizeClass(uptr alloc_size) { + uptr rounded_size = RoundUpToPowerOfTwo(alloc_size); + uptr log = Log2(rounded_size); + CHECK(alloc_size <= (1UL << log)); + if (!(alloc_size > (1UL << (log-1)))) { + Printf("alloc_size %zu log %zu\n", alloc_size, log); + } + CHECK(alloc_size > (1UL << (log-1))); + uptr res = log < kMinStackFrameSizeLog ? 0 : log - kMinStackFrameSizeLog; + CHECK(res < kNumberOfSizeClasses); + CHECK(ClassSize(res) >= rounded_size); + return res; +} + +void FakeFrameFifo::FifoPush(FakeFrame *node) { + CHECK(node); + node->next = 0; + if (first_ == 0 && last_ == 0) { + first_ = last_ = node; + } else { + CHECK(first_); + CHECK(last_); + last_->next = node; + last_ = node; + } +} + +FakeFrame *FakeFrameFifo::FifoPop() { + CHECK(first_ && last_ && "Exhausted fake stack"); + FakeFrame *res = 0; + if (first_ == last_) { + res = first_; + first_ = last_ = 0; + } else { + res = first_; + first_ = first_->next; + } + return res; +} + +void FakeStack::Init(uptr stack_size) { + stack_size_ = stack_size; + alive_ = true; +} + +void FakeStack::Cleanup() { + alive_ = false; + for (uptr i = 0; i < kNumberOfSizeClasses; i++) { + uptr mem = allocated_size_classes_[i]; + if (mem) { + PoisonShadow(mem, ClassMmapSize(i), 0); + allocated_size_classes_[i] = 0; + UnmapOrDie((void*)mem, ClassMmapSize(i)); + } + } +} + +uptr FakeStack::ClassMmapSize(uptr size_class) { + return RoundUpToPowerOfTwo(stack_size_); +} + +void FakeStack::AllocateOneSizeClass(uptr size_class) { + CHECK(ClassMmapSize(size_class) >= GetPageSizeCached()); + uptr new_mem = (uptr)MmapOrDie( + ClassMmapSize(size_class), __FUNCTION__); + // Printf("T%d new_mem[%zu]: %p-%p mmap %zu\n", + // asanThreadRegistry().GetCurrent()->tid(), + // size_class, new_mem, new_mem + ClassMmapSize(size_class), + // ClassMmapSize(size_class)); + uptr i; + for (i = 0; i < ClassMmapSize(size_class); + i += ClassSize(size_class)) { + size_classes_[size_class].FifoPush((FakeFrame*)(new_mem + i)); + } + CHECK(i == ClassMmapSize(size_class)); + allocated_size_classes_[size_class] = new_mem; +} + +uptr FakeStack::AllocateStack(uptr size, uptr real_stack) { + if (!alive_) return real_stack; + CHECK(size <= kMaxStackMallocSize && size > 1); + uptr size_class = ComputeSizeClass(size); + if (!allocated_size_classes_[size_class]) { + AllocateOneSizeClass(size_class); + } + FakeFrame *fake_frame = size_classes_[size_class].FifoPop(); + CHECK(fake_frame); + fake_frame->size_minus_one = size - 1; + fake_frame->real_stack = real_stack; + while (FakeFrame *top = call_stack_.top()) { + if (top->real_stack > real_stack) break; + call_stack_.LifoPop(); + DeallocateFrame(top); + } + call_stack_.LifoPush(fake_frame); + uptr ptr = (uptr)fake_frame; + PoisonShadow(ptr, size, 0); + return ptr; +} + +void FakeStack::DeallocateFrame(FakeFrame *fake_frame) { + CHECK(alive_); + uptr size = fake_frame->size_minus_one + 1; + uptr size_class = ComputeSizeClass(size); + CHECK(allocated_size_classes_[size_class]); + uptr ptr = (uptr)fake_frame; + CHECK(AddrIsInSizeClass(ptr, size_class)); + CHECK(AddrIsInSizeClass(ptr + size - 1, size_class)); + size_classes_[size_class].FifoPush(fake_frame); +} + +void FakeStack::OnFree(uptr ptr, uptr size, uptr real_stack) { + FakeFrame *fake_frame = (FakeFrame*)ptr; + CHECK(fake_frame->magic = kRetiredStackFrameMagic); + CHECK(fake_frame->descr != 0); + CHECK(fake_frame->size_minus_one == size - 1); + PoisonShadow(ptr, size, kAsanStackAfterReturnMagic); +} + +} // namespace __asan + +// ---------------------- Interface ---------------- {{{1 +using namespace __asan; // NOLINT + +uptr __asan_stack_malloc(uptr size, uptr real_stack) { + if (!flags()->use_fake_stack) return real_stack; + AsanThread *t = asanThreadRegistry().GetCurrent(); + if (!t) { + // TSD is gone, use the real stack. + return real_stack; + } + uptr ptr = t->fake_stack().AllocateStack(size, real_stack); + // Printf("__asan_stack_malloc %p %zu %p\n", ptr, size, real_stack); + return ptr; +} + +void __asan_stack_free(uptr ptr, uptr size, uptr real_stack) { + if (!flags()->use_fake_stack) return; + if (ptr != real_stack) { + FakeStack::OnFree(ptr, size, real_stack); + } +} diff --git a/lib/asan/asan_flags.h b/lib/asan/asan_flags.h index ca9cf84ba6c5..d7b21ea4a45f 100644 --- a/lib/asan/asan_flags.h +++ b/lib/asan/asan_flags.h @@ -15,7 +15,7 @@ #ifndef ASAN_FLAGS_H #define ASAN_FLAGS_H -#include "sanitizer_common/sanitizer_interface_defs.h" +#include "sanitizer/common_interface_defs.h" // ASan flag values can be defined in three ways: // 1) initialized with default values at startup. @@ -23,11 +23,6 @@ // __asan_default_options(). // 3) overriden from env variable ASAN_OPTIONS. -extern "C" { -// Can be overriden by user. -const char *__asan_default_options() SANITIZER_WEAK_ATTRIBUTE; -} // extern "C" - namespace __asan { struct Flags { @@ -48,7 +43,9 @@ struct Flags { // on globals, 1 - detect buffer overflow, 2 - print data about registered // globals). int report_globals; - // Max number of stack frames kept for each allocation. + // If set, attempts to catch initialization order issues. + bool check_initialization_order; + // Max number of stack frames kept for each allocation/deallocation. int malloc_context_size; // If set, uses custom wrappers and replacements for libc string functions // to find more errors. @@ -87,6 +84,28 @@ struct Flags { // By default, disable core dumper on 64-bit - it makes little sense // to dump 16T+ core. bool disable_core; + // Allow the tool to re-exec the program. This may interfere badly with the + // debugger. + bool allow_reexec; + // Strips this prefix from file paths in error reports. + const char *strip_path_prefix; + // If set, prints not only thread creation stacks for threads in error report, + // but also thread creation stacks for threads that created those threads, + // etc. up to main thread. + bool print_full_thread_history; + // ASan will write logs to "log_path.pid" instead of stderr. + const char *log_path; + // Use fast (frame-pointer-based) unwinder on fatal errors (if available). + bool fast_unwind_on_fatal; + // Use fast (frame-pointer-based) unwinder on malloc/free (if available). + bool fast_unwind_on_malloc; + // Poison (or not) the heap memory on [de]allocation. Zero value is useful + // for benchmarking the allocator or instrumentator. + bool poison_heap; + // Report errors on malloc/delete, new/free, new/delete[], etc. + bool alloc_dealloc_mismatch; + // Use stack depot instead of storing stacks in the redzones. + bool use_stack_depot; }; Flags *flags(); diff --git a/lib/asan/asan_globals.cc b/lib/asan/asan_globals.cc index f8c4040b8e86..4e18bb8e2355 100644 --- a/lib/asan/asan_globals.cc +++ b/lib/asan/asan_globals.cc @@ -12,15 +12,14 @@ // Handle globals. //===----------------------------------------------------------------------===// #include "asan_interceptors.h" -#include "asan_interface.h" #include "asan_internal.h" -#include "asan_lock.h" #include "asan_mapping.h" +#include "asan_report.h" #include "asan_stack.h" #include "asan_stats.h" #include "asan_thread.h" - -#include <ctype.h> +#include "sanitizer/asan_interface.h" +#include "sanitizer_common/sanitizer_mutex.h" namespace __asan { @@ -31,9 +30,10 @@ struct ListOfGlobals { ListOfGlobals *next; }; -static AsanLock mu_for_globals(LINKER_INITIALIZED); -static ListOfGlobals *list_of_globals; -static LowLevelAllocator allocator_for_globals(LINKER_INITIALIZED); +static BlockingMutex mu_for_globals(LINKER_INITIALIZED); +static LowLevelAllocator allocator_for_globals; +static ListOfGlobals *list_of_all_globals; +static ListOfGlobals *list_of_dynamic_init_globals; void PoisonRedZones(const Global &g) { uptr shadow_rz_size = kGlobalAndStackRedzone >> SHADOW_SCALE; @@ -55,48 +55,16 @@ void PoisonRedZones(const Global &g) { } } -static uptr GetAlignedSize(uptr size) { - return ((size + kGlobalAndStackRedzone - 1) / kGlobalAndStackRedzone) - * kGlobalAndStackRedzone; -} - - // Check if the global is a zero-terminated ASCII string. If so, print it. -void PrintIfASCII(const Global &g) { - for (uptr p = g.beg; p < g.beg + g.size - 1; p++) { - if (!isascii(*(char*)p)) return; - } - if (*(char*)(g.beg + g.size - 1) != 0) return; - AsanPrintf(" '%s' is ascii string '%s'\n", g.name, (char*)g.beg); -} - -bool DescribeAddrIfMyRedZone(const Global &g, uptr addr) { - if (addr < g.beg - kGlobalAndStackRedzone) return false; - if (addr >= g.beg + g.size_with_redzone) return false; - AsanPrintf("%p is located ", (void*)addr); - if (addr < g.beg) { - AsanPrintf("%zd bytes to the left", g.beg - addr); - } else if (addr >= g.beg + g.size) { - AsanPrintf("%zd bytes to the right", addr - (g.beg + g.size)); - } else { - AsanPrintf("%zd bytes inside", addr - g.beg); // Can it happen? - } - AsanPrintf(" of global variable '%s' (0x%zx) of size %zu\n", - g.name, g.beg, g.size); - PrintIfASCII(g); - return true; -} - - -bool DescribeAddrIfGlobal(uptr addr) { +bool DescribeAddressIfGlobal(uptr addr) { if (!flags()->report_globals) return false; - ScopedLock lock(&mu_for_globals); + BlockingMutexLock lock(&mu_for_globals); bool res = false; - for (ListOfGlobals *l = list_of_globals; l; l = l->next) { + for (ListOfGlobals *l = list_of_all_globals; l; l = l->next) { const Global &g = *l->g; if (flags()->report_globals >= 2) - AsanPrintf("Search Global: beg=%p size=%zu name=%s\n", - (void*)g.beg, g.size, (char*)g.name); - res |= DescribeAddrIfMyRedZone(g, addr); + Report("Search Global: beg=%p size=%zu name=%s\n", + (void*)g.beg, g.size, (char*)g.name); + res |= DescribeAddressRelativeToGlobal(addr, g); } return res; } @@ -106,6 +74,10 @@ bool DescribeAddrIfGlobal(uptr addr) { // so we store the globals in a map. static void RegisterGlobal(const Global *g) { CHECK(asan_inited); + if (flags()->report_globals >= 2) + Report("Added Global: beg=%p size=%zu/%zu name=%s dyn.init=%zu\n", + (void*)g->beg, g->size, g->size_with_redzone, g->name, + g->has_dynamic_init); CHECK(flags()->report_globals); CHECK(AddrIsInMem(g->beg)); CHECK(AddrIsAlignedByGranularity(g->beg)); @@ -114,11 +86,14 @@ static void RegisterGlobal(const Global *g) { ListOfGlobals *l = (ListOfGlobals*)allocator_for_globals.Allocate(sizeof(ListOfGlobals)); l->g = g; - l->next = list_of_globals; - list_of_globals = l; - if (flags()->report_globals >= 2) - Report("Added Global: beg=%p size=%zu name=%s\n", - (void*)g->beg, g->size, g->name); + l->next = list_of_all_globals; + list_of_all_globals = l; + if (g->has_dynamic_init) { + l = (ListOfGlobals*)allocator_for_globals.Allocate(sizeof(ListOfGlobals)); + l->g = g; + l->next = list_of_dynamic_init_globals; + list_of_dynamic_init_globals = l; + } } static void UnregisterGlobal(const Global *g) { @@ -133,39 +108,83 @@ static void UnregisterGlobal(const Global *g) { // implementation. It might not be worth doing anyway. } +// Poison all shadow memory for a single global. +static void PoisonGlobalAndRedzones(const Global *g) { + CHECK(asan_inited); + CHECK(flags()->check_initialization_order); + CHECK(AddrIsInMem(g->beg)); + CHECK(AddrIsAlignedByGranularity(g->beg)); + CHECK(AddrIsAlignedByGranularity(g->size_with_redzone)); + if (flags()->report_globals >= 3) + Printf("DynInitPoison : %s\n", g->name); + PoisonShadow(g->beg, g->size_with_redzone, kAsanInitializationOrderMagic); +} + +static void UnpoisonGlobal(const Global *g) { + CHECK(asan_inited); + CHECK(flags()->check_initialization_order); + CHECK(AddrIsInMem(g->beg)); + CHECK(AddrIsAlignedByGranularity(g->beg)); + CHECK(AddrIsAlignedByGranularity(g->size_with_redzone)); + if (flags()->report_globals >= 3) + Printf("DynInitUnpoison: %s\n", g->name); + PoisonShadow(g->beg, g->size_with_redzone, 0); + PoisonRedZones(*g); +} + } // namespace __asan // ---------------------- Interface ---------------- {{{1 using namespace __asan; // NOLINT -// Register one global with a default redzone. -void __asan_register_global(uptr addr, uptr size, - const char *name) { - if (!flags()->report_globals) return; - ScopedLock lock(&mu_for_globals); - Global *g = (Global *)allocator_for_globals.Allocate(sizeof(Global)); - g->beg = addr; - g->size = size; - g->size_with_redzone = GetAlignedSize(size) + kGlobalAndStackRedzone; - g->name = name; - RegisterGlobal(g); -} - // Register an array of globals. void __asan_register_globals(__asan_global *globals, uptr n) { if (!flags()->report_globals) return; - ScopedLock lock(&mu_for_globals); + BlockingMutexLock lock(&mu_for_globals); for (uptr i = 0; i < n; i++) { RegisterGlobal(&globals[i]); } } // Unregister an array of globals. -// We must do it when a shared objects gets dlclosed. +// We must do this when a shared objects gets dlclosed. void __asan_unregister_globals(__asan_global *globals, uptr n) { if (!flags()->report_globals) return; - ScopedLock lock(&mu_for_globals); + BlockingMutexLock lock(&mu_for_globals); for (uptr i = 0; i < n; i++) { UnregisterGlobal(&globals[i]); } } + +// This method runs immediately prior to dynamic initialization in each TU, +// when all dynamically initialized globals are unpoisoned. This method +// poisons all global variables not defined in this TU, so that a dynamic +// initializer can only touch global variables in the same TU. +void __asan_before_dynamic_init(uptr first_addr, uptr last_addr) { + if (!flags()->check_initialization_order) return; + CHECK(list_of_dynamic_init_globals); + BlockingMutexLock lock(&mu_for_globals); + bool from_current_tu = false; + // The list looks like: + // a => ... => b => last_addr => ... => first_addr => c => ... + // The globals of the current TU reside between last_addr and first_addr. + for (ListOfGlobals *l = list_of_dynamic_init_globals; l; l = l->next) { + if (l->g->beg == last_addr) + from_current_tu = true; + if (!from_current_tu) + PoisonGlobalAndRedzones(l->g); + if (l->g->beg == first_addr) + from_current_tu = false; + } + CHECK(!from_current_tu); +} + +// This method runs immediately after dynamic initialization in each TU, when +// all dynamically initialized globals except for those defined in the current +// TU are poisoned. It simply unpoisons all dynamically initialized globals. +void __asan_after_dynamic_init() { + if (!flags()->check_initialization_order) return; + BlockingMutexLock lock(&mu_for_globals); + for (ListOfGlobals *l = list_of_dynamic_init_globals; l; l = l->next) + UnpoisonGlobal(l->g); +} diff --git a/lib/asan/asan_intercepted_functions.h b/lib/asan/asan_intercepted_functions.h new file mode 100644 index 000000000000..a1faf713c130 --- /dev/null +++ b/lib/asan/asan_intercepted_functions.h @@ -0,0 +1,260 @@ +//===-- asan_intercepted_functions.h ----------------------------*- C++ -*-===// +// +// 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. +// +// ASan-private header containing prototypes for wrapper functions and wrappers +//===----------------------------------------------------------------------===// +#ifndef ASAN_INTERCEPTED_FUNCTIONS_H +#define ASAN_INTERCEPTED_FUNCTIONS_H + +#include "asan_internal.h" +#include "interception/interception.h" +#include "sanitizer_common/sanitizer_platform_interceptors.h" + +#include <stdarg.h> + +using __sanitizer::uptr; + +// Use macro to describe if specific function should be +// intercepted on a given platform. +#if !defined(_WIN32) +# define ASAN_INTERCEPT_ATOLL_AND_STRTOLL 1 +# define ASAN_INTERCEPT__LONGJMP 1 +# define ASAN_INTERCEPT_STRDUP 1 +# define ASAN_INTERCEPT_STRCASECMP_AND_STRNCASECMP 1 +# define ASAN_INTERCEPT_INDEX 1 +# define ASAN_INTERCEPT_PTHREAD_CREATE 1 +# define ASAN_INTERCEPT_MLOCKX 1 +#else +# define ASAN_INTERCEPT_ATOLL_AND_STRTOLL 0 +# define ASAN_INTERCEPT__LONGJMP 0 +# define ASAN_INTERCEPT_STRDUP 0 +# define ASAN_INTERCEPT_STRCASECMP_AND_STRNCASECMP 0 +# define ASAN_INTERCEPT_INDEX 0 +# define ASAN_INTERCEPT_PTHREAD_CREATE 0 +# define ASAN_INTERCEPT_MLOCKX 0 +#endif + +#if defined(__linux__) +# define ASAN_USE_ALIAS_ATTRIBUTE_FOR_INDEX 1 +#else +# define ASAN_USE_ALIAS_ATTRIBUTE_FOR_INDEX 0 +#endif + +#if !defined(__APPLE__) +# define ASAN_INTERCEPT_STRNLEN 1 +#else +# define ASAN_INTERCEPT_STRNLEN 0 +#endif + +#if defined(__linux__) && !defined(ANDROID) +# define ASAN_INTERCEPT_SWAPCONTEXT 1 +#else +# define ASAN_INTERCEPT_SWAPCONTEXT 0 +#endif + +#if !defined(ANDROID) && !defined(_WIN32) +# define ASAN_INTERCEPT_SIGNAL_AND_SIGACTION 1 +#else +# define ASAN_INTERCEPT_SIGNAL_AND_SIGACTION 0 +#endif + +// On Darwin siglongjmp tailcalls longjmp, so we don't want to intercept it +// there. +#if !defined(_WIN32) && (!defined(__APPLE__) || MAC_INTERPOSE_FUNCTIONS) +# define ASAN_INTERCEPT_SIGLONGJMP 1 +#else +# define ASAN_INTERCEPT_SIGLONGJMP 0 +#endif + +#if ASAN_HAS_EXCEPTIONS && !defined(_WIN32) +# define ASAN_INTERCEPT___CXA_THROW 1 +#else +# define ASAN_INTERCEPT___CXA_THROW 0 +#endif + +#define DECLARE_FUNCTION_AND_WRAPPER(ret_type, func, ...) \ + ret_type func(__VA_ARGS__); \ + ret_type WRAP(func)(__VA_ARGS__) + +// Use extern declarations of intercepted functions on Mac and Windows +// to avoid including system headers. +#if defined(__APPLE__) || (defined(_WIN32) && !defined(_DLL)) +extern "C" { +// signal.h +# if ASAN_INTERCEPT_SIGNAL_AND_SIGACTION +struct sigaction; +DECLARE_FUNCTION_AND_WRAPPER(int, sigaction, int sig, + const struct sigaction *act, + struct sigaction *oldact); +DECLARE_FUNCTION_AND_WRAPPER(void*, signal, int signum, void *handler); +# endif + +// setjmp.h +DECLARE_FUNCTION_AND_WRAPPER(void, longjmp, void *env, int value); +# if ASAN_INTERCEPT__LONGJMP +DECLARE_FUNCTION_AND_WRAPPER(void, _longjmp, void *env, int value); +# endif +# if ASAN_INTERCEPT_SIGLONGJMP +DECLARE_FUNCTION_AND_WRAPPER(void, siglongjmp, void *env, int value); +# endif +# if ASAN_INTERCEPT___CXA_THROW +DECLARE_FUNCTION_AND_WRAPPER(void, __cxa_throw, void *a, void *b, void *c); +#endif + +// string.h / strings.h +DECLARE_FUNCTION_AND_WRAPPER(int, memcmp, + const void *a1, const void *a2, uptr size); +DECLARE_FUNCTION_AND_WRAPPER(void*, memmove, + void *to, const void *from, uptr size); +DECLARE_FUNCTION_AND_WRAPPER(void*, memcpy, + void *to, const void *from, uptr size); +DECLARE_FUNCTION_AND_WRAPPER(void*, memset, void *block, int c, uptr size); +DECLARE_FUNCTION_AND_WRAPPER(char*, strchr, const char *str, int c); +DECLARE_FUNCTION_AND_WRAPPER(char*, strcat, /* NOLINT */ + char *to, const char* from); +DECLARE_FUNCTION_AND_WRAPPER(char*, strncat, + char *to, const char* from, uptr size); +DECLARE_FUNCTION_AND_WRAPPER(char*, strcpy, /* NOLINT */ + char *to, const char* from); +DECLARE_FUNCTION_AND_WRAPPER(char*, strncpy, + char *to, const char* from, uptr size); +DECLARE_FUNCTION_AND_WRAPPER(int, strcmp, const char *s1, const char* s2); +DECLARE_FUNCTION_AND_WRAPPER(int, strncmp, + const char *s1, const char* s2, uptr size); +DECLARE_FUNCTION_AND_WRAPPER(uptr, strlen, const char *s); +# if ASAN_INTERCEPT_STRCASECMP_AND_STRNCASECMP +DECLARE_FUNCTION_AND_WRAPPER(int, strcasecmp, const char *s1, const char *s2); +DECLARE_FUNCTION_AND_WRAPPER(int, strncasecmp, + const char *s1, const char *s2, uptr n); +# endif +# if ASAN_INTERCEPT_STRDUP +DECLARE_FUNCTION_AND_WRAPPER(char*, strdup, const char *s); +# endif +# if ASAN_INTERCEPT_STRNLEN +DECLARE_FUNCTION_AND_WRAPPER(uptr, strnlen, const char *s, uptr maxlen); +# endif +#if ASAN_INTERCEPT_INDEX +DECLARE_FUNCTION_AND_WRAPPER(char*, index, const char *string, int c); +#endif + +// stdlib.h +DECLARE_FUNCTION_AND_WRAPPER(int, atoi, const char *nptr); +DECLARE_FUNCTION_AND_WRAPPER(long, atol, const char *nptr); // NOLINT +DECLARE_FUNCTION_AND_WRAPPER(long, strtol, const char *nptr, char **endptr, int base); // NOLINT +# if ASAN_INTERCEPT_ATOLL_AND_STRTOLL +DECLARE_FUNCTION_AND_WRAPPER(long long, atoll, const char *nptr); // NOLINT +DECLARE_FUNCTION_AND_WRAPPER(long long, strtoll, const char *nptr, char **endptr, int base); // NOLINT +# endif + +// unistd.h +# if SANITIZER_INTERCEPT_READ +DECLARE_FUNCTION_AND_WRAPPER(SSIZE_T, read, int fd, void *buf, SIZE_T count); +# endif +# if SANITIZER_INTERCEPT_PREAD +DECLARE_FUNCTION_AND_WRAPPER(SSIZE_T, pread, int fd, void *buf, + SIZE_T count, OFF_T offset); +# endif +# if SANITIZER_INTERCEPT_PREAD64 +DECLARE_FUNCTION_AND_WRAPPER(SSIZE_T, pread64, int fd, void *buf, + SIZE_T count, OFF64_T offset); +# endif + +#if SANITIZER_INTERCEPT_WRITE +DECLARE_FUNCTION_AND_WRAPPER(SSIZE_T, write, int fd, void *ptr, SIZE_T count); +#endif +#if SANITIZER_INTERCEPT_PWRITE +DECLARE_FUNCTION_AND_WRAPPER(SSIZE_T, pwrite, int fd, void *ptr, SIZE_T count); +#endif + +# if ASAN_INTERCEPT_MLOCKX +// mlock/munlock +DECLARE_FUNCTION_AND_WRAPPER(int, mlock, const void *addr, SIZE_T len); +DECLARE_FUNCTION_AND_WRAPPER(int, munlock, const void *addr, SIZE_T len); +DECLARE_FUNCTION_AND_WRAPPER(int, mlockall, int flags); +DECLARE_FUNCTION_AND_WRAPPER(int, munlockall, void); +# endif + +// Windows threads. +# if defined(_WIN32) +__declspec(dllimport) +void* __stdcall CreateThread(void *sec, uptr st, void* start, + void *arg, DWORD fl, DWORD *id); +# endif +// Posix threads. +# if ASAN_INTERCEPT_PTHREAD_CREATE +DECLARE_FUNCTION_AND_WRAPPER(int, pthread_create, + void *thread, void *attr, + void *(*start_routine)(void*), void *arg); +# endif + +#if defined(__APPLE__) +typedef void* pthread_workqueue_t; +typedef void* pthread_workitem_handle_t; + +typedef void* dispatch_group_t; +typedef void* dispatch_queue_t; +typedef void* dispatch_source_t; +typedef u64 dispatch_time_t; +typedef void (*dispatch_function_t)(void *block); +typedef void* (*worker_t)(void *block); +typedef void* CFStringRef; +typedef void* CFAllocatorRef; + +DECLARE_FUNCTION_AND_WRAPPER(void, dispatch_async_f, + dispatch_queue_t dq, + void *ctxt, dispatch_function_t func); +DECLARE_FUNCTION_AND_WRAPPER(void, dispatch_sync_f, + dispatch_queue_t dq, + void *ctxt, dispatch_function_t func); +DECLARE_FUNCTION_AND_WRAPPER(void, dispatch_after_f, + dispatch_time_t when, dispatch_queue_t dq, + void *ctxt, dispatch_function_t func); +DECLARE_FUNCTION_AND_WRAPPER(void, dispatch_barrier_async_f, + dispatch_queue_t dq, + void *ctxt, dispatch_function_t func); +DECLARE_FUNCTION_AND_WRAPPER(void, dispatch_group_async_f, + dispatch_group_t group, dispatch_queue_t dq, + void *ctxt, dispatch_function_t func); + +DECLARE_FUNCTION_AND_WRAPPER(void, __CFInitialize, void); +DECLARE_FUNCTION_AND_WRAPPER(CFStringRef, CFStringCreateCopy, + CFAllocatorRef alloc, CFStringRef str); +DECLARE_FUNCTION_AND_WRAPPER(void, free, void* ptr); + +DECLARE_FUNCTION_AND_WRAPPER(int, vscanf, const char *format, va_list ap); +DECLARE_FUNCTION_AND_WRAPPER(int, vsscanf, const char *str, const char *format, + va_list ap); +DECLARE_FUNCTION_AND_WRAPPER(int, vfscanf, void *stream, const char *format, + va_list ap); +DECLARE_FUNCTION_AND_WRAPPER(int, scanf, const char *format, ...); +DECLARE_FUNCTION_AND_WRAPPER(int, fscanf, + void* stream, const char *format, ...); +DECLARE_FUNCTION_AND_WRAPPER(int, sscanf, // NOLINT + const char *str, const char *format, ...); + +#if MAC_INTERPOSE_FUNCTIONS && !defined(MISSING_BLOCKS_SUPPORT) +DECLARE_FUNCTION_AND_WRAPPER(void, dispatch_group_async, + dispatch_group_t dg, + dispatch_queue_t dq, void (^work)(void)); +DECLARE_FUNCTION_AND_WRAPPER(void, dispatch_async, + dispatch_queue_t dq, void (^work)(void)); +DECLARE_FUNCTION_AND_WRAPPER(void, dispatch_after, + dispatch_queue_t dq, void (^work)(void)); +DECLARE_FUNCTION_AND_WRAPPER(void, dispatch_source_set_event_handler, + dispatch_source_t ds, void (^work)(void)); +DECLARE_FUNCTION_AND_WRAPPER(void, dispatch_source_set_cancel_handler, + dispatch_source_t ds, void (^work)(void)); +#endif // MAC_INTERPOSE_FUNCTIONS +#endif // __APPLE__ +} // extern "C" +#endif + +#endif // ASAN_INTERCEPTED_FUNCTIONS_H diff --git a/lib/asan/asan_interceptors.cc b/lib/asan/asan_interceptors.cc index 2ce5826e1495..6170974d6f5e 100644 --- a/lib/asan/asan_interceptors.cc +++ b/lib/asan/asan_interceptors.cc @@ -14,136 +14,33 @@ #include "asan_interceptors.h" #include "asan_allocator.h" -#include "asan_interface.h" +#include "asan_intercepted_functions.h" #include "asan_internal.h" #include "asan_mapping.h" +#include "asan_report.h" #include "asan_stack.h" #include "asan_stats.h" #include "asan_thread_registry.h" #include "interception/interception.h" +#include "sanitizer/asan_interface.h" #include "sanitizer_common/sanitizer_libc.h" -// Use macro to describe if specific function should be -// intercepted on a given platform. -#if !defined(_WIN32) -# define ASAN_INTERCEPT_ATOLL_AND_STRTOLL 1 -#else -# define ASAN_INTERCEPT_ATOLL_AND_STRTOLL 0 -#endif - -#if !defined(__APPLE__) -# define ASAN_INTERCEPT_STRNLEN 1 -#else -# define ASAN_INTERCEPT_STRNLEN 0 -#endif - -#if defined(ANDROID) || defined(_WIN32) -# define ASAN_INTERCEPT_SIGNAL_AND_SIGACTION 0 -#else -# define ASAN_INTERCEPT_SIGNAL_AND_SIGACTION 1 -#endif - -// Use extern declarations of intercepted functions on Mac and Windows -// to avoid including system headers. -#if defined(__APPLE__) || (defined(_WIN32) && !defined(_DLL)) -extern "C" { -// signal.h -# if ASAN_INTERCEPT_SIGNAL_AND_SIGACTION -struct sigaction; -int sigaction(int sig, const struct sigaction *act, - struct sigaction *oldact); -void *signal(int signum, void *handler); -# endif - -// setjmp.h -void longjmp(void* env, int value); -# if !defined(_WIN32) -void _longjmp(void *env, int value); -# endif - -// string.h / strings.h -int memcmp(const void *a1, const void *a2, uptr size); -void* memmove(void *to, const void *from, uptr size); -void* memcpy(void *to, const void *from, uptr size); -void* memset(void *block, int c, uptr size); -char* strchr(const char *str, int c); -# if defined(__APPLE__) -char* index(const char *string, int c); -# endif -char* strcat(char *to, const char* from); // NOLINT -char *strncat(char *to, const char* from, uptr size); -char* strcpy(char *to, const char* from); // NOLINT -char* strncpy(char *to, const char* from, uptr size); -int strcmp(const char *s1, const char* s2); -int strncmp(const char *s1, const char* s2, uptr size); -# if !defined(_WIN32) -int strcasecmp(const char *s1, const char *s2); -int strncasecmp(const char *s1, const char *s2, uptr n); -char* strdup(const char *s); -# endif -uptr strlen(const char *s); -# if ASAN_INTERCEPT_STRNLEN -uptr strnlen(const char *s, uptr maxlen); -# endif - -// stdlib.h -int atoi(const char *nptr); -long atol(const char *nptr); // NOLINT -long strtol(const char *nptr, char **endptr, int base); // NOLINT -# if ASAN_INTERCEPT_ATOLL_AND_STRTOLL -long long atoll(const char *nptr); // NOLINT -long long strtoll(const char *nptr, char **endptr, int base); // NOLINT -# endif - -// Windows threads. -# if defined(_WIN32) -__declspec(dllimport) -void* __stdcall CreateThread(void *sec, uptr st, void* start, - void *arg, DWORD fl, DWORD *id); -# endif - -// Posix threads. -# if !defined(_WIN32) -int pthread_create(void *thread, void *attr, void *(*start_routine)(void*), - void *arg); -# endif -} // extern "C" -#endif - namespace __asan { -// Instruments read/write access to a single byte in memory. -// On error calls __asan_report_error, which aborts the program. -#define ACCESS_ADDRESS(address, isWrite) do { \ - if (!AddrIsInMem(address) || AddressIsPoisoned(address)) { \ - GET_CURRENT_PC_BP_SP; \ - __asan_report_error(pc, bp, sp, address, isWrite, /* access_size */ 1); \ - } \ -} while (0) - // We implement ACCESS_MEMORY_RANGE, ASAN_READ_RANGE, // and ASAN_WRITE_RANGE as macro instead of function so // that no extra frames are created, and stack trace contains // relevant information only. - -// Instruments read/write access to a memory range. -// More complex implementation is possible, for now just -// checking the first and the last byte of a range. -#define ACCESS_MEMORY_RANGE(offset, size, isWrite) do { \ - if (size > 0) { \ - uptr ptr = (uptr)(offset); \ - ACCESS_ADDRESS(ptr, isWrite); \ - ACCESS_ADDRESS(ptr + (size) - 1, isWrite); \ - } \ +// We check all shadow bytes. +#define ACCESS_MEMORY_RANGE(offset, size, isWrite) do { \ + if (uptr __ptr = __asan_region_is_poisoned((uptr)(offset), size)) { \ + GET_CURRENT_PC_BP_SP; \ + __asan_report_error(pc, bp, sp, __ptr, isWrite, /* access_size */1); \ + } \ } while (0) -#define ASAN_READ_RANGE(offset, size) do { \ - ACCESS_MEMORY_RANGE(offset, size, false); \ -} while (0) - -#define ASAN_WRITE_RANGE(offset, size) do { \ - ACCESS_MEMORY_RANGE(offset, size, true); \ -} while (0) +#define ASAN_READ_RANGE(offset, size) ACCESS_MEMORY_RANGE(offset, size, false) +#define ASAN_WRITE_RANGE(offset, size) ACCESS_MEMORY_RANGE(offset, size, true); // Behavior of functions like "memcpy" or "strcpy" is undefined // if memory intervals overlap. We report error in this case. @@ -156,11 +53,9 @@ static inline bool RangesOverlap(const char *offset1, uptr length1, const char *offset1 = (const char*)_offset1; \ const char *offset2 = (const char*)_offset2; \ if (RangesOverlap(offset1, length1, offset2, length2)) { \ - AsanReport("ERROR: AddressSanitizer %s-param-overlap: " \ - "memory ranges [%p,%p) and [%p, %p) overlap\n", \ - name, offset1, offset1 + length1, offset2, offset2 + length2); \ - PRINT_CURRENT_STACK(); \ - ShowStatsAndAbort(); \ + GET_STACK_TRACE_FATAL_HERE; \ + ReportStringFunctionMemoryRangesOverlap(name, offset1, length1, \ + offset2, length2, &stack); \ } \ } while (0) @@ -180,27 +75,47 @@ static inline uptr MaybeRealStrnlen(const char *s, uptr maxlen) { return internal_strnlen(s, maxlen); } +void SetThreadName(const char *name) { + AsanThread *t = asanThreadRegistry().GetCurrent(); + if (t) + t->summary()->set_name(name); +} + } // namespace __asan // ---------------------- Wrappers ---------------- {{{1 using namespace __asan; // NOLINT +#define COMMON_INTERCEPTOR_WRITE_RANGE(ctx, ptr, size) \ + ASAN_WRITE_RANGE(ptr, size) +#define COMMON_INTERCEPTOR_READ_RANGE(ctx, ptr, size) ASAN_READ_RANGE(ptr, size) +#define COMMON_INTERCEPTOR_ENTER(ctx, func, ...) \ + do { \ + ctx = 0; \ + (void)ctx; \ + ENSURE_ASAN_INITED(); \ + } while (false) +#define COMMON_INTERCEPTOR_FD_ACQUIRE(ctx, fd) do { } while (false) +#define COMMON_INTERCEPTOR_FD_RELEASE(ctx, fd) do { } while (false) +#define COMMON_INTERCEPTOR_SET_THREAD_NAME(ctx, name) SetThreadName(name) +#include "sanitizer_common/sanitizer_common_interceptors.inc" + static thread_return_t THREAD_CALLING_CONV asan_thread_start(void *arg) { AsanThread *t = (AsanThread*)arg; asanThreadRegistry().SetCurrent(t); return t->ThreadStart(); } -#ifndef _WIN32 +#if ASAN_INTERCEPT_PTHREAD_CREATE INTERCEPTOR(int, pthread_create, void *thread, void *attr, void *(*start_routine)(void*), void *arg) { - GET_STACK_TRACE_HERE(kStackTraceMax); + GET_STACK_TRACE_THREAD; u32 current_tid = asanThreadRegistry().GetCurrentTidOrInvalid(); AsanThread *t = AsanThread::Create(current_tid, start_routine, arg, &stack); asanThreadRegistry().RegisterThread(t); return REAL(pthread_create)(thread, attr, asan_thread_start, t); } -#endif // !_WIN32 +#endif // ASAN_INTERCEPT_PTHREAD_CREATE #if ASAN_INTERCEPT_SIGNAL_AND_SIGACTION INTERCEPTOR(void*, signal, int signum, void *handler) { @@ -223,28 +138,62 @@ DEFINE_REAL(int, sigaction, int signum, const struct sigaction *act, struct sigaction *oldact); #endif // ASAN_INTERCEPT_SIGNAL_AND_SIGACTION +#if ASAN_INTERCEPT_SWAPCONTEXT +static void ClearShadowMemoryForContextStack(uptr stack, uptr ssize) { + // Align to page size. + uptr PageSize = GetPageSizeCached(); + uptr bottom = stack & ~(PageSize - 1); + ssize += stack - bottom; + ssize = RoundUpTo(ssize, PageSize); + static const uptr kMaxSaneContextStackSize = 1 << 22; // 4 Mb + if (ssize && ssize <= kMaxSaneContextStackSize) { + PoisonShadow(bottom, ssize, 0); + } +} + +INTERCEPTOR(int, swapcontext, struct ucontext_t *oucp, + struct ucontext_t *ucp) { + static bool reported_warning = false; + if (!reported_warning) { + Report("WARNING: ASan doesn't fully support makecontext/swapcontext " + "functions and may produce false positives in some cases!\n"); + reported_warning = true; + } + // Clear shadow memory for new context (it may share stack + // with current context). + uptr stack, ssize; + ReadContextStack(ucp, &stack, &ssize); + ClearShadowMemoryForContextStack(stack, ssize); + int res = REAL(swapcontext)(oucp, ucp); + // swapcontext technically does not return, but program may swap context to + // "oucp" later, that would look as if swapcontext() returned 0. + // We need to clear shadow for ucp once again, as it may be in arbitrary + // state. + ClearShadowMemoryForContextStack(stack, ssize); + return res; +} +#endif // ASAN_INTERCEPT_SWAPCONTEXT + INTERCEPTOR(void, longjmp, void *env, int val) { __asan_handle_no_return(); REAL(longjmp)(env, val); } -#if !defined(_WIN32) +#if ASAN_INTERCEPT__LONGJMP INTERCEPTOR(void, _longjmp, void *env, int val) { __asan_handle_no_return(); REAL(_longjmp)(env, val); } +#endif +#if ASAN_INTERCEPT_SIGLONGJMP INTERCEPTOR(void, siglongjmp, void *env, int val) { __asan_handle_no_return(); REAL(siglongjmp)(env, val); } #endif -#if ASAN_HAS_EXCEPTIONS == 1 -#ifdef __APPLE__ -extern "C" void __cxa_throw(void *a, void *b, void *c); -#endif // __APPLE__ - +#if ASAN_INTERCEPT___CXA_THROW INTERCEPTOR(void, __cxa_throw, void *a, void *b, void *c) { CHECK(REAL(__cxa_throw)); __asan_handle_no_return(); @@ -263,26 +212,22 @@ static void MlockIsUnsupported() { } extern "C" { -INTERCEPTOR_ATTRIBUTE -int mlock(const void *addr, uptr len) { +INTERCEPTOR(int, mlock, const void *addr, uptr len) { MlockIsUnsupported(); return 0; } -INTERCEPTOR_ATTRIBUTE -int munlock(const void *addr, uptr len) { +INTERCEPTOR(int, munlock, const void *addr, uptr len) { MlockIsUnsupported(); return 0; } -INTERCEPTOR_ATTRIBUTE -int mlockall(int flags) { +INTERCEPTOR(int, mlockall, int flags) { MlockIsUnsupported(); return 0; } -INTERCEPTOR_ATTRIBUTE -int munlockall(void) { +INTERCEPTOR(int, munlockall, void) { MlockIsUnsupported(); return 0; } @@ -299,6 +244,7 @@ static inline int CharCaseCmp(unsigned char c1, unsigned char c2) { } INTERCEPTOR(int, memcmp, const void *a1, const void *a2, uptr size) { + if (!asan_inited) return internal_memcmp(a1, a2, size); ENSURE_ASAN_INITED(); unsigned char c1 = 0, c2 = 0; const unsigned char *s1 = (const unsigned char*)a1; @@ -315,6 +261,7 @@ INTERCEPTOR(int, memcmp, const void *a1, const void *a2, uptr size) { } INTERCEPTOR(void*, memcpy, void *to, const void *from, uptr size) { + if (!asan_inited) return internal_memcpy(to, from, size); // memcpy is called during __asan_init() from the internals // of printf(...). if (asan_init_is_running) { @@ -327,25 +274,39 @@ INTERCEPTOR(void*, memcpy, void *to, const void *from, uptr size) { // See http://llvm.org/bugs/show_bug.cgi?id=11763. CHECK_RANGES_OVERLAP("memcpy", to, size, from, size); } - ASAN_WRITE_RANGE(from, size); - ASAN_READ_RANGE(to, size); + ASAN_READ_RANGE(from, size); + ASAN_WRITE_RANGE(to, size); } +#if MAC_INTERPOSE_FUNCTIONS + // Interposing of resolver functions is broken on Mac OS 10.7 and 10.8. + // See also http://code.google.com/p/address-sanitizer/issues/detail?id=116. + return internal_memcpy(to, from, size); +#else return REAL(memcpy)(to, from, size); +#endif } INTERCEPTOR(void*, memmove, void *to, const void *from, uptr size) { + if (!asan_inited) return internal_memmove(to, from, size); if (asan_init_is_running) { return REAL(memmove)(to, from, size); } ENSURE_ASAN_INITED(); if (flags()->replace_intrin) { - ASAN_WRITE_RANGE(from, size); - ASAN_READ_RANGE(to, size); + ASAN_READ_RANGE(from, size); + ASAN_WRITE_RANGE(to, size); } +#if MAC_INTERPOSE_FUNCTIONS + // Interposing of resolver functions is broken on Mac OS 10.7 and 10.8. + // See also http://code.google.com/p/address-sanitizer/issues/detail?id=116. + return internal_memmove(to, from, size); +#else return REAL(memmove)(to, from, size); +#endif } INTERCEPTOR(void*, memset, void *block, int c, uptr size) { + if (!asan_inited) return internal_memset(block, c, size); // memset is called inside Printf. if (asan_init_is_running) { return REAL(memset)(block, c, size); @@ -358,6 +319,12 @@ INTERCEPTOR(void*, memset, void *block, int c, uptr size) { } INTERCEPTOR(char*, strchr, const char *str, int c) { + if (!asan_inited) return internal_strchr(str, c); + // strchr is called inside create_purgeable_zone() when MallocGuardEdges=1 is + // used. + if (asan_init_is_running) { + return REAL(strchr)(str, c); + } ENSURE_ASAN_INITED(); char *result = REAL(strchr)(str, c); if (flags()->replace_str) { @@ -367,37 +334,31 @@ INTERCEPTOR(char*, strchr, const char *str, int c) { return result; } -#ifdef __linux__ +#if ASAN_INTERCEPT_INDEX +# if ASAN_USE_ALIAS_ATTRIBUTE_FOR_INDEX INTERCEPTOR(char*, index, const char *string, int c) ALIAS(WRAPPER_NAME(strchr)); -#else +# else DEFINE_REAL(char*, index, const char *string, int c) -#endif - -INTERCEPTOR(int, strcasecmp, const char *s1, const char *s2) { - ENSURE_ASAN_INITED(); - unsigned char c1, c2; - uptr i; - for (i = 0; ; i++) { - c1 = (unsigned char)s1[i]; - c2 = (unsigned char)s2[i]; - if (CharCaseCmp(c1, c2) != 0 || c1 == '\0') break; - } - ASAN_READ_RANGE(s1, i + 1); - ASAN_READ_RANGE(s2, i + 1); - return CharCaseCmp(c1, c2); -} +# endif +#endif // ASAN_INTERCEPT_INDEX +// For both strcat() and strncat() we need to check the validity of |to| +// argument irrespective of the |from| length. INTERCEPTOR(char*, strcat, char *to, const char *from) { // NOLINT ENSURE_ASAN_INITED(); if (flags()->replace_str) { uptr from_length = REAL(strlen)(from); ASAN_READ_RANGE(from, from_length + 1); + uptr to_length = REAL(strlen)(to); + ASAN_READ_RANGE(to, to_length); + ASAN_WRITE_RANGE(to + to_length, from_length + 1); + // If the copying actually happens, the |from| string should not overlap + // with the resulting string starting at |to|, which has a length of + // to_length + from_length + 1. if (from_length > 0) { - uptr to_length = REAL(strlen)(to); - ASAN_READ_RANGE(to, to_length); - ASAN_WRITE_RANGE(to + to_length, from_length + 1); - CHECK_RANGES_OVERLAP("strcat", to, to_length + 1, from, from_length + 1); + CHECK_RANGES_OVERLAP("strcat", to, from_length + to_length + 1, + from, from_length + 1); } } return REAL(strcat)(to, from); // NOLINT @@ -405,23 +366,25 @@ INTERCEPTOR(char*, strcat, char *to, const char *from) { // NOLINT INTERCEPTOR(char*, strncat, char *to, const char *from, uptr size) { ENSURE_ASAN_INITED(); - if (flags()->replace_str && size > 0) { + if (flags()->replace_str) { uptr from_length = MaybeRealStrnlen(from, size); - ASAN_READ_RANGE(from, Min(size, from_length + 1)); + uptr copy_length = Min(size, from_length + 1); + ASAN_READ_RANGE(from, copy_length); uptr to_length = REAL(strlen)(to); ASAN_READ_RANGE(to, to_length); ASAN_WRITE_RANGE(to + to_length, from_length + 1); if (from_length > 0) { - CHECK_RANGES_OVERLAP("strncat", to, to_length + 1, - from, Min(size, from_length + 1)); + CHECK_RANGES_OVERLAP("strncat", to, to_length + copy_length + 1, + from, copy_length); } } return REAL(strncat)(to, from, size); } INTERCEPTOR(int, strcmp, const char *s1, const char *s2) { - if (!asan_inited) { - return internal_strcmp(s1, s2); + if (!asan_inited) return internal_strcmp(s1, s2); + if (asan_init_is_running) { + return REAL(strcmp)(s1, s2); } ENSURE_ASAN_INITED(); unsigned char c1, c2; @@ -437,6 +400,9 @@ INTERCEPTOR(int, strcmp, const char *s1, const char *s2) { } INTERCEPTOR(char*, strcpy, char *to, const char *from) { // NOLINT +#if MAC_INTERPOSE_FUNCTIONS + if (!asan_inited) return REAL(strcpy)(to, from); // NOLINT +#endif // strcpy is called from malloc_default_purgeable_zone() // in __asan::ReplaceSystemAlloc() on Mac. if (asan_init_is_running) { @@ -452,7 +418,17 @@ INTERCEPTOR(char*, strcpy, char *to, const char *from) { // NOLINT return REAL(strcpy)(to, from); // NOLINT } +#if ASAN_INTERCEPT_STRDUP INTERCEPTOR(char*, strdup, const char *s) { +#if MAC_INTERPOSE_FUNCTIONS + // FIXME: because internal_strdup() uses InternalAlloc(), which currently + // just calls malloc() on Mac, we can't use internal_strdup() with the + // dynamic runtime. We can remove the call to REAL(strdup) once InternalAlloc + // starts using mmap() instead. + // See also http://code.google.com/p/address-sanitizer/issues/detail?id=123. + if (!asan_inited) return REAL(strdup)(s); +#endif + if (!asan_inited) return internal_strdup(s); ENSURE_ASAN_INITED(); if (flags()->replace_str) { uptr length = REAL(strlen)(s); @@ -460,8 +436,10 @@ INTERCEPTOR(char*, strdup, const char *s) { } return REAL(strdup)(s); } +#endif INTERCEPTOR(uptr, strlen, const char *s) { + if (!asan_inited) return internal_strlen(s); // strlen is called from malloc_default_purgeable_zone() // in __asan::ReplaceSystemAlloc() on Mac. if (asan_init_is_running) { @@ -475,6 +453,21 @@ INTERCEPTOR(uptr, strlen, const char *s) { return length; } +#if ASAN_INTERCEPT_STRCASECMP_AND_STRNCASECMP +INTERCEPTOR(int, strcasecmp, const char *s1, const char *s2) { + ENSURE_ASAN_INITED(); + unsigned char c1, c2; + uptr i; + for (i = 0; ; i++) { + c1 = (unsigned char)s1[i]; + c2 = (unsigned char)s2[i]; + if (CharCaseCmp(c1, c2) != 0 || c1 == '\0') break; + } + ASAN_READ_RANGE(s1, i + 1); + ASAN_READ_RANGE(s2, i + 1); + return CharCaseCmp(c1, c2); +} + INTERCEPTOR(int, strncasecmp, const char *s1, const char *s2, uptr n) { ENSURE_ASAN_INITED(); unsigned char c1 = 0, c2 = 0; @@ -488,8 +481,10 @@ INTERCEPTOR(int, strncasecmp, const char *s1, const char *s2, uptr n) { ASAN_READ_RANGE(s2, Min(i + 1, n)); return CharCaseCmp(c1, c2); } +#endif // ASAN_INTERCEPT_STRCASECMP_AND_STRNCASECMP INTERCEPTOR(int, strncmp, const char *s1, const char *s2, uptr size) { + if (!asan_inited) return internal_strncmp(s1, s2, size); // strncmp is called from malloc_default_purgeable_zone() // in __asan::ReplaceSystemAlloc() on Mac. if (asan_init_is_running) { @@ -566,6 +561,9 @@ INTERCEPTOR(long, strtol, const char *nptr, // NOLINT } INTERCEPTOR(int, atoi, const char *nptr) { +#if MAC_INTERPOSE_FUNCTIONS + if (!asan_inited) return REAL(atoi)(nptr); +#endif ENSURE_ASAN_INITED(); if (!flags()->replace_str) { return REAL(atoi)(nptr); @@ -582,6 +580,9 @@ INTERCEPTOR(int, atoi, const char *nptr) { } INTERCEPTOR(long, atol, const char *nptr) { // NOLINT +#if MAC_INTERPOSE_FUNCTIONS + if (!asan_inited) return REAL(atol)(nptr); +#endif ENSURE_ASAN_INITED(); if (!flags()->replace_str) { return REAL(atol)(nptr); @@ -638,7 +639,7 @@ INTERCEPTOR_WINAPI(DWORD, CreateThread, void* security, uptr stack_size, DWORD (__stdcall *start_routine)(void*), void* arg, DWORD flags, void* tid) { - GET_STACK_TRACE_HERE(kStackTraceMax); + GET_STACK_TRACE_THREAD; u32 current_tid = asanThreadRegistry().GetCurrentTidOrInvalid(); AsanThread *t = AsanThread::Create(current_tid, start_routine, arg, &stack); asanThreadRegistry().RegisterThread(t); @@ -660,6 +661,12 @@ void InitializeAsanInterceptors() { static bool was_called_once; CHECK(was_called_once == false); was_called_once = true; +#if MAC_INTERPOSE_FUNCTIONS + return; +#endif + + SANITIZER_COMMON_INTERCEPTORS_INIT; + // Intercept mem* functions. ASAN_INTERCEPT_FUNC(memcmp); ASAN_INTERCEPT_FUNC(memmove); @@ -667,7 +674,11 @@ void InitializeAsanInterceptors() { if (PLATFORM_HAS_DIFFERENT_MEMCPY_AND_MEMMOVE) { ASAN_INTERCEPT_FUNC(memcpy); } else { - REAL(memcpy) = REAL(memmove); +#if !MAC_INTERPOSE_FUNCTIONS + // If we're using dynamic interceptors on Mac, these two are just plain + // functions. + internal_memcpy(&REAL(memcpy), &REAL(memmove), sizeof(REAL(memmove))); +#endif } // Intercept str* functions. @@ -679,19 +690,23 @@ void InitializeAsanInterceptors() { ASAN_INTERCEPT_FUNC(strncat); ASAN_INTERCEPT_FUNC(strncmp); ASAN_INTERCEPT_FUNC(strncpy); -#if !defined(_WIN32) +#if ASAN_INTERCEPT_STRCASECMP_AND_STRNCASECMP ASAN_INTERCEPT_FUNC(strcasecmp); - ASAN_INTERCEPT_FUNC(strdup); ASAN_INTERCEPT_FUNC(strncasecmp); -# ifndef __APPLE__ +#endif +#if ASAN_INTERCEPT_STRDUP + ASAN_INTERCEPT_FUNC(strdup); +#endif +#if ASAN_INTERCEPT_STRNLEN + ASAN_INTERCEPT_FUNC(strnlen); +#endif +#if ASAN_INTERCEPT_INDEX +# if ASAN_USE_ALIAS_ATTRIBUTE_FOR_INDEX ASAN_INTERCEPT_FUNC(index); # else CHECK(OVERRIDE_FUNCTION(index, WRAP(strchr))); # endif #endif -#if ASAN_INTERCEPT_STRNLEN - ASAN_INTERCEPT_FUNC(strnlen); -#endif ASAN_INTERCEPT_FUNC(atoi); ASAN_INTERCEPT_FUNC(atol); @@ -701,25 +716,37 @@ void InitializeAsanInterceptors() { ASAN_INTERCEPT_FUNC(strtoll); #endif +#if ASAN_INTERCEPT_MLOCKX + // Intercept mlock/munlock. + ASAN_INTERCEPT_FUNC(mlock); + ASAN_INTERCEPT_FUNC(munlock); + ASAN_INTERCEPT_FUNC(mlockall); + ASAN_INTERCEPT_FUNC(munlockall); +#endif + // Intecept signal- and jump-related functions. ASAN_INTERCEPT_FUNC(longjmp); #if ASAN_INTERCEPT_SIGNAL_AND_SIGACTION ASAN_INTERCEPT_FUNC(sigaction); ASAN_INTERCEPT_FUNC(signal); #endif - -#if !defined(_WIN32) +#if ASAN_INTERCEPT_SWAPCONTEXT + ASAN_INTERCEPT_FUNC(swapcontext); +#endif +#if ASAN_INTERCEPT__LONGJMP ASAN_INTERCEPT_FUNC(_longjmp); - INTERCEPT_FUNCTION(__cxa_throw); -# if !defined(__APPLE__) - // On Darwin siglongjmp tailcalls longjmp, so we don't want to intercept it - // there. +#endif +#if ASAN_INTERCEPT_SIGLONGJMP ASAN_INTERCEPT_FUNC(siglongjmp); -# endif +#endif + + // Intercept exception handling functions. +#if ASAN_INTERCEPT___CXA_THROW + INTERCEPT_FUNCTION(__cxa_throw); #endif // Intercept threading-related functions -#if !defined(_WIN32) +#if ASAN_INTERCEPT_PTHREAD_CREATE ASAN_INTERCEPT_FUNC(pthread_create); #endif diff --git a/lib/asan/asan_interceptors.h b/lib/asan/asan_interceptors.h index 32816920f7a3..3b3e90ef93ff 100644 --- a/lib/asan/asan_interceptors.h +++ b/lib/asan/asan_interceptors.h @@ -24,6 +24,7 @@ DECLARE_REAL(char*, strchr, const char *str, int c) DECLARE_REAL(uptr, strlen, const char *s) DECLARE_REAL(char*, strncpy, char *to, const char *from, uptr size) DECLARE_REAL(uptr, strnlen, const char *s, uptr maxlen) +DECLARE_REAL(char*, strstr, const char *s1, const char *s2) struct sigaction; DECLARE_REAL(int, sigaction, int signum, const struct sigaction *act, struct sigaction *oldact) diff --git a/lib/asan/asan_internal.h b/lib/asan/asan_internal.h index 8c1f32028f2c..5d3bffa814da 100644 --- a/lib/asan/asan_internal.h +++ b/lib/asan/asan_internal.h @@ -17,17 +17,13 @@ #include "asan_flags.h" #include "sanitizer_common/sanitizer_common.h" #include "sanitizer_common/sanitizer_internal_defs.h" +#include "sanitizer_common/sanitizer_stacktrace.h" #include "sanitizer_common/sanitizer_libc.h" #if !defined(__linux__) && !defined(__APPLE__) && !defined(_WIN32) # error "This operating system is not supported by AddressSanitizer" #endif -#if defined(_WIN32) -extern "C" void* _ReturnAddress(void); -# pragma intrinsic(_ReturnAddress) -#endif // defined(_WIN32) - #define ASAN_DEFAULT_FAILURE_EXITCODE 1 #if defined(__linux__) @@ -48,6 +44,13 @@ extern "C" void* _ReturnAddress(void); # define ASAN_WINDOWS 0 #endif +#if defined(__ANDROID__) || defined(ANDROID) +# define ASAN_ANDROID 1 +#else +# define ASAN_ANDROID 0 +#endif + + #define ASAN_POSIX (ASAN_LINUX || ASAN_MAC) #if __has_feature(address_sanitizer) @@ -59,7 +62,11 @@ extern "C" void* _ReturnAddress(void); // If set, asan will install its own SEGV signal handler. #ifndef ASAN_NEEDS_SEGV -# define ASAN_NEEDS_SEGV 1 +# if ASAN_ANDROID == 1 +# define ASAN_NEEDS_SEGV 0 +# else +# define ASAN_NEEDS_SEGV 1 +# endif #endif // If set, asan will intercept C++ exception api call(s). @@ -76,7 +83,11 @@ extern "C" void* _ReturnAddress(void); // If set, values like allocator chunk size, as well as defaults for some flags // will be changed towards less memory overhead. #ifndef ASAN_LOW_MEMORY -# define ASAN_LOW_MEMORY 0 +#if SANITIZER_WORDSIZE == 32 +# define ASAN_LOW_MEMORY 1 +#else +# define ASAN_LOW_MEMORY 0 +# endif #endif // All internal functions in asan reside inside the __asan namespace @@ -86,14 +97,11 @@ extern "C" void* _ReturnAddress(void); namespace __asan { class AsanThread; -struct AsanStackTrace; +using __sanitizer::StackTrace; // asan_rtl.cc void NORETURN ShowStatsAndAbort(); -// asan_globals.cc -bool DescribeAddrIfGlobal(uptr addr); - void ReplaceOperatorsNewAndDelete(); // asan_malloc_linux.cc / asan_malloc_mac.cc void ReplaceSystemMalloc(); @@ -103,10 +111,12 @@ void *AsanDoesNotSupportStaticLinkage(); void GetPcSpBp(void *context, uptr *pc, uptr *sp, uptr *bp); +void MaybeReexec(); bool AsanInterceptsSignal(int signum); void SetAlternateSignalStack(); void UnsetAlternateSignalStack(); void InstallSignalHandlers(); +void ReadContextStack(void *context, uptr *stack, uptr *ssize); void AsanPlatformThreadInit(); // Wrapper for TLS/TSD. @@ -115,9 +125,6 @@ void *AsanTSDGet(); void AsanTSDSet(void *tsd); void AppendToErrorMessageBuffer(const char *buffer); -// asan_printf.cc -void AsanPrintf(const char *format, ...); -void AsanReport(const char *format, ...); // asan_poisoning.cc // Poisons the shadow memory for "size" bytes starting from "addr". @@ -138,33 +145,20 @@ bool PlatformHasDifferentMemcpyAndMemmove(); # define PLATFORM_HAS_DIFFERENT_MEMCPY_AND_MEMMOVE true #endif // __APPLE__ +// Add convenient macro for interface functions that may be represented as +// weak hooks. +#define ASAN_MALLOC_HOOK(ptr, size) \ + if (&__asan_malloc_hook) __asan_malloc_hook(ptr, size) +#define ASAN_FREE_HOOK(ptr) \ + if (&__asan_free_hook) __asan_free_hook(ptr) +#define ASAN_ON_ERROR() \ + if (&__asan_on_error) __asan_on_error() + extern int asan_inited; // Used to avoid infinite recursion in __asan_init(). extern bool asan_init_is_running; extern void (*death_callback)(void); -enum LinkerInitialized { LINKER_INITIALIZED = 0 }; - -#define ASAN_ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0])) - -#if !defined(_WIN32) || defined(__clang__) -# define GET_CALLER_PC() (uptr)__builtin_return_address(0) -# define GET_CURRENT_FRAME() (uptr)__builtin_frame_address(0) -#else -# define GET_CALLER_PC() (uptr)_ReturnAddress() -// CaptureStackBackTrace doesn't need to know BP on Windows. -// FIXME: This macro is still used when printing error reports though it's not -// clear if the BP value is needed in the ASan reports on Windows. -# define GET_CURRENT_FRAME() (uptr)0xDEADBEEF -#endif - -#ifdef _WIN32 -# ifndef ASAN_USE_EXTERNAL_SYMBOLIZER -# define ASAN_USE_EXTERNAL_SYMBOLIZER __asan_WinSymbolize -bool __asan_WinSymbolize(const void *addr, char *out_buffer, int buffer_size); -# endif -#endif // _WIN32 - // These magic values are written to shadow for better error reporting. const int kAsanHeapLeftRedzoneMagic = 0xfa; const int kAsanHeapRightRedzoneMagic = 0xfb; @@ -174,26 +168,15 @@ const int kAsanStackMidRedzoneMagic = 0xf2; const int kAsanStackRightRedzoneMagic = 0xf3; const int kAsanStackPartialRedzoneMagic = 0xf4; const int kAsanStackAfterReturnMagic = 0xf5; +const int kAsanInitializationOrderMagic = 0xf6; const int kAsanUserPoisonedMemoryMagic = 0xf7; +const int kAsanStackUseAfterScopeMagic = 0xf8; const int kAsanGlobalRedzoneMagic = 0xf9; const int kAsanInternalHeapMagic = 0xfe; static const uptr kCurrentStackFrameMagic = 0x41B58AB3; static const uptr kRetiredStackFrameMagic = 0x45E0360E; -// -------------------------- LowLevelAllocator ----- {{{1 -// A simple low-level memory allocator for internal use. -class LowLevelAllocator { - public: - explicit LowLevelAllocator(LinkerInitialized) {} - // 'size' must be a power of two. - // Requires an external lock. - void *Allocate(uptr size); - private: - char *allocated_end_; - char *allocated_current_; -}; - } // namespace __asan #endif // ASAN_INTERNAL_H diff --git a/lib/asan/asan_linux.cc b/lib/asan/asan_linux.cc index 9a3d6bdb15a6..845493de0956 100644 --- a/lib/asan/asan_linux.cc +++ b/lib/asan/asan_linux.cc @@ -15,8 +15,8 @@ #include "asan_interceptors.h" #include "asan_internal.h" -#include "asan_lock.h" #include "asan_thread.h" +#include "asan_thread_registry.h" #include "sanitizer_common/sanitizer_libc.h" #include "sanitizer_common/sanitizer_procmaps.h" @@ -31,7 +31,7 @@ #include <unistd.h> #include <unwind.h> -#ifndef ANDROID +#if !ASAN_ANDROID // FIXME: where to get ucontext on Android? #include <sys/ucontext.h> #endif @@ -40,13 +40,17 @@ extern "C" void* _DYNAMIC; namespace __asan { +void MaybeReexec() { + // No need to re-exec on Linux. +} + void *AsanDoesNotSupportStaticLinkage() { // This will fail to link with -static. return &_DYNAMIC; // defined in link.h } void GetPcSpBp(void *context, uptr *pc, uptr *sp, uptr *bp) { -#ifdef ANDROID +#if ASAN_ANDROID *pc = *sp = *bp = 0; #elif defined(__arm__) ucontext_t *ucontext = (ucontext_t*)context; @@ -63,6 +67,27 @@ void GetPcSpBp(void *context, uptr *pc, uptr *sp, uptr *bp) { *pc = ucontext->uc_mcontext.gregs[REG_EIP]; *bp = ucontext->uc_mcontext.gregs[REG_EBP]; *sp = ucontext->uc_mcontext.gregs[REG_ESP]; +# elif defined(__powerpc__) || defined(__powerpc64__) + ucontext_t *ucontext = (ucontext_t*)context; + *pc = ucontext->uc_mcontext.regs->nip; + *sp = ucontext->uc_mcontext.regs->gpr[PT_R1]; + // The powerpc{,64}-linux ABIs do not specify r31 as the frame + // pointer, but GCC always uses r31 when we need a frame pointer. + *bp = ucontext->uc_mcontext.regs->gpr[PT_R31]; +# elif defined(__sparc__) + ucontext_t *ucontext = (ucontext_t*)context; + uptr *stk_ptr; +# if defined (__arch64__) + *pc = ucontext->uc_mcontext.mc_gregs[MC_PC]; + *sp = ucontext->uc_mcontext.mc_gregs[MC_O6]; + stk_ptr = (uptr *) (*sp + 2047); + *bp = stk_ptr[15]; +# else + *pc = ucontext->uc_mcontext.gregs[REG_PC]; + *sp = ucontext->uc_mcontext.gregs[REG_O6]; + stk_ptr = (uptr *) *sp; + *bp = stk_ptr[15]; +# endif #else # error "Unsupported arch" #endif @@ -76,69 +101,35 @@ void AsanPlatformThreadInit() { // Nothing here for now. } -AsanLock::AsanLock(LinkerInitialized) { - // We assume that pthread_mutex_t initialized to all zeroes is a valid - // unlocked mutex. We can not use PTHREAD_MUTEX_INITIALIZER as it triggers - // a gcc warning: - // extended initializer lists only available with -std=c++0x or -std=gnu++0x -} - -void AsanLock::Lock() { - CHECK(sizeof(pthread_mutex_t) <= sizeof(opaque_storage_)); - pthread_mutex_lock((pthread_mutex_t*)&opaque_storage_); - CHECK(!owner_); - owner_ = (uptr)pthread_self(); -} - -void AsanLock::Unlock() { - CHECK(owner_ == (uptr)pthread_self()); - owner_ = 0; - pthread_mutex_unlock((pthread_mutex_t*)&opaque_storage_); -} - -#ifdef __arm__ -#define UNWIND_STOP _URC_END_OF_STACK -#define UNWIND_CONTINUE _URC_NO_REASON -#else -#define UNWIND_STOP _URC_NORMAL_STOP -#define UNWIND_CONTINUE _URC_NO_REASON -#endif - -uptr Unwind_GetIP(struct _Unwind_Context *ctx) { -#ifdef __arm__ - uptr val; - _Unwind_VRS_Result res = _Unwind_VRS_Get(ctx, _UVRSC_CORE, - 15 /* r15 = PC */, _UVRSD_UINT32, &val); - CHECK(res == _UVRSR_OK && "_Unwind_VRS_Get failed"); - // Clear the Thumb bit. - return val & ~(uptr)1; -#else - return _Unwind_GetIP(ctx); +void GetStackTrace(StackTrace *stack, uptr max_s, uptr pc, uptr bp, bool fast) { +#if defined(__arm__) || \ + defined(__powerpc__) || defined(__powerpc64__) || \ + defined(__sparc__) + fast = false; #endif + if (!fast) + return stack->SlowUnwindStack(pc, max_s); + stack->size = 0; + stack->trace[0] = pc; + if (max_s > 1) { + stack->max_size = max_s; + if (!asan_inited) return; + if (AsanThread *t = asanThreadRegistry().GetCurrent()) + stack->FastUnwindStack(pc, bp, t->stack_top(), t->stack_bottom()); + } } -_Unwind_Reason_Code Unwind_Trace(struct _Unwind_Context *ctx, - void *param) { - AsanStackTrace *b = (AsanStackTrace*)param; - CHECK(b->size < b->max_size); - uptr pc = Unwind_GetIP(ctx); - b->trace[b->size++] = pc; - if (b->size == b->max_size) return UNWIND_STOP; - return UNWIND_CONTINUE; +#if !ASAN_ANDROID +void ReadContextStack(void *context, uptr *stack, uptr *ssize) { + ucontext_t *ucp = (ucontext_t*)context; + *stack = (uptr)ucp->uc_stack.ss_sp; + *ssize = ucp->uc_stack.ss_size; } - -void AsanStackTrace::GetStackTrace(uptr max_s, uptr pc, uptr bp) { - size = 0; - trace[0] = pc; - if ((max_s) > 1) { - max_size = max_s; -#ifdef __arm__ - _Unwind_Backtrace(Unwind_Trace, this); #else - FastUnwindStack(pc, bp); -#endif - } +void ReadContextStack(void *context, uptr *stack, uptr *ssize) { + UNIMPLEMENTED(); } +#endif } // namespace __asan diff --git a/lib/asan/asan_lock.h b/lib/asan/asan_lock.h index edee49adf6a7..e69de29bb2d1 100644 --- a/lib/asan/asan_lock.h +++ b/lib/asan/asan_lock.h @@ -1,42 +0,0 @@ -//===-- asan_lock.h ---------------------------------------------*- C++ -*-===// -// -// 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. -// -// A wrapper for a simple lock. -//===----------------------------------------------------------------------===// -#ifndef ASAN_LOCK_H -#define ASAN_LOCK_H - -#include "sanitizer_common/sanitizer_mutex.h" -#include "asan_internal.h" - -// The locks in ASan are global objects and they are never destroyed to avoid -// at-exit races (that is, a lock is being used by other threads while the main -// thread is doing atexit destructors). -// We define the class using opaque storage to avoid including system headers. - -namespace __asan { - -class AsanLock { - public: - explicit AsanLock(LinkerInitialized); - void Lock(); - void Unlock(); - bool IsLocked() { return owner_ != 0; } - private: - uptr opaque_storage_[10]; - uptr owner_; // for debugging and for malloc_introspection_t interface -}; - -typedef GenericScopedLock<AsanLock> ScopedLock; - -} // namespace __asan - -#endif // ASAN_LOCK_H diff --git a/lib/asan/asan_mac.cc b/lib/asan/asan_mac.cc index a3d39e7ae05c..3ed9e06eeb6f 100644 --- a/lib/asan/asan_mac.cc +++ b/lib/asan/asan_mac.cc @@ -23,7 +23,8 @@ #include "asan_thread_registry.h" #include "sanitizer_common/sanitizer_libc.h" -#include <crt_externs.h> // for _NSGetEnviron +#include <crt_externs.h> // for _NSGetArgv +#include <dlfcn.h> // for dladdr() #include <mach-o/dyld.h> #include <mach-o/loader.h> #include <sys/mman.h> @@ -41,7 +42,7 @@ namespace __asan { void GetPcSpBp(void *context, uptr *pc, uptr *sp, uptr *bp) { ucontext_t *ucontext = (ucontext_t*)context; -# if __WORDSIZE == 64 +# if SANITIZER_WORDSIZE == 64 *pc = ucontext->uc_mcontext->__ss.__rip; *bp = ucontext->uc_mcontext->__ss.__rbp; *sp = ucontext->uc_mcontext->__ss.__rsp; @@ -49,7 +50,7 @@ void GetPcSpBp(void *context, uptr *pc, uptr *sp, uptr *bp) { *pc = ucontext->uc_mcontext->__ss.__eip; *bp = ucontext->uc_mcontext->__ss.__ebp; *sp = ucontext->uc_mcontext->__ss.__esp; -# endif // __WORDSIZE +# endif // SANITIZER_WORDSIZE } int GetMacosVersion() { @@ -67,6 +68,7 @@ int GetMacosVersion() { switch (version[1]) { case '0': return MACOS_VERSION_SNOW_LEOPARD; case '1': return MACOS_VERSION_LION; + case '2': return MACOS_VERSION_MOUNTAIN_LION; default: return MACOS_VERSION_UNKNOWN; } } @@ -83,6 +85,42 @@ bool PlatformHasDifferentMemcpyAndMemmove() { return GetMacosVersion() == MACOS_VERSION_SNOW_LEOPARD; } +extern "C" +void __asan_init(); + +static const char kDyldInsertLibraries[] = "DYLD_INSERT_LIBRARIES"; + +void MaybeReexec() { + if (!flags()->allow_reexec) return; +#if MAC_INTERPOSE_FUNCTIONS + // If the program is linked with the dynamic ASan runtime library, make sure + // the library is preloaded so that the wrappers work. If it is not, set + // DYLD_INSERT_LIBRARIES and re-exec ourselves. + Dl_info info; + CHECK(dladdr((void*)((uptr)__asan_init), &info)); + const char *dyld_insert_libraries = GetEnv(kDyldInsertLibraries); + if (!dyld_insert_libraries || + !REAL(strstr)(dyld_insert_libraries, info.dli_fname)) { + // DYLD_INSERT_LIBRARIES is not set or does not contain the runtime + // library. + char program_name[1024]; + uint32_t buf_size = sizeof(program_name); + _NSGetExecutablePath(program_name, &buf_size); + // Ok to use setenv() since the wrappers don't depend on the value of + // asan_inited. + setenv(kDyldInsertLibraries, info.dli_fname, /*overwrite*/0); + if (flags()->verbosity >= 1) { + Report("exec()-ing the program with\n"); + Report("%s=%s\n", kDyldInsertLibraries, info.dli_fname); + Report("to enable ASan wrappers.\n"); + Report("Set ASAN_OPTIONS=allow_reexec=0 to disable this.\n"); + } + execv(program_name, *_NSGetArgv()); + } +#endif // MAC_INTERPOSE_FUNCTIONS + // If we're not using the dynamic runtime, do nothing. +} + // No-op. Mac does not support static linkage anyway. void *AsanDoesNotSupportStaticLinkage() { return 0; @@ -93,37 +131,32 @@ bool AsanInterceptsSignal(int signum) { } void AsanPlatformThreadInit() { - ReplaceCFAllocator(); -} - -AsanLock::AsanLock(LinkerInitialized) { - // We assume that OS_SPINLOCK_INIT is zero -} - -void AsanLock::Lock() { - CHECK(sizeof(OSSpinLock) <= sizeof(opaque_storage_)); - CHECK(OS_SPINLOCK_INIT == 0); - CHECK(owner_ != (uptr)pthread_self()); - OSSpinLockLock((OSSpinLock*)&opaque_storage_); - CHECK(!owner_); - owner_ = (uptr)pthread_self(); -} - -void AsanLock::Unlock() { - CHECK(owner_ == (uptr)pthread_self()); - owner_ = 0; - OSSpinLockUnlock((OSSpinLock*)&opaque_storage_); + // For the first program thread, we can't replace the allocator before + // __CFInitialize() has been called. If it hasn't, we'll call + // MaybeReplaceCFAllocator() later on this thread. + // For other threads __CFInitialize() has been called before their creation. + // See also asan_malloc_mac.cc. + if (((CFRuntimeBase*)kCFAllocatorSystemDefault)->_cfisa) { + MaybeReplaceCFAllocator(); + } } -void AsanStackTrace::GetStackTrace(uptr max_s, uptr pc, uptr bp) { - size = 0; - trace[0] = pc; +void GetStackTrace(StackTrace *stack, uptr max_s, uptr pc, uptr bp, bool fast) { + (void)fast; + stack->size = 0; + stack->trace[0] = pc; if ((max_s) > 1) { - max_size = max_s; - FastUnwindStack(pc, bp); + stack->max_size = max_s; + if (!asan_inited) return; + if (AsanThread *t = asanThreadRegistry().GetCurrent()) + stack->FastUnwindStack(pc, bp, t->stack_top(), t->stack_bottom()); } } +void ReadContextStack(void *context, uptr *stack, uptr *ssize) { + UNIMPLEMENTED(); +} + // The range of pages to be used for escape islands. // TODO(glider): instead of mapping a fixed range we must find a range of // unmapped pages in vmmap and take them. @@ -132,12 +165,12 @@ void AsanStackTrace::GetStackTrace(uptr max_s, uptr pc, uptr bp) { // kHighMemBeg or kHighMemEnd. static void *island_allocator_pos = 0; -#if __WORDSIZE == 32 -# define kIslandEnd (0xffdf0000 - kPageSize) -# define kIslandBeg (kIslandEnd - 256 * kPageSize) +#if SANITIZER_WORDSIZE == 32 +# define kIslandEnd (0xffdf0000 - GetPageSizeCached()) +# define kIslandBeg (kIslandEnd - 256 * GetPageSizeCached()) #else -# define kIslandEnd (0x7fffffdf0000 - kPageSize) -# define kIslandBeg (kIslandEnd - 256 * kPageSize) +# define kIslandEnd (0x7fffffdf0000 - GetPageSizeCached()) +# define kIslandBeg (kIslandEnd - 256 * GetPageSizeCached()) #endif extern "C" @@ -161,7 +194,7 @@ mach_error_t __interception_allocate_island(void **ptr, internal_memset(island_allocator_pos, 0xCC, kIslandEnd - kIslandBeg); }; *ptr = island_allocator_pos; - island_allocator_pos = (char*)island_allocator_pos + kPageSize; + island_allocator_pos = (char*)island_allocator_pos + GetPageSizeCached(); if (flags()->verbosity) { Report("Branch island allocated at %p\n", *ptr); } @@ -209,6 +242,7 @@ typedef void* pthread_workitem_handle_t; typedef void* dispatch_group_t; typedef void* dispatch_queue_t; +typedef void* dispatch_source_t; typedef u64 dispatch_time_t; typedef void (*dispatch_function_t)(void *block); typedef void* (*worker_t)(void *block); @@ -236,30 +270,34 @@ void dispatch_barrier_async_f(dispatch_queue_t dq, void *ctxt, dispatch_function_t func); void dispatch_group_async_f(dispatch_group_t group, dispatch_queue_t dq, void *ctxt, dispatch_function_t func); -int pthread_workqueue_additem_np(pthread_workqueue_t workq, - void *(*workitem_func)(void *), void * workitem_arg, - pthread_workitem_handle_t * itemhandlep, unsigned int *gencountp); } // extern "C" +static ALWAYS_INLINE +void asan_register_worker_thread(int parent_tid, StackTrace *stack) { + AsanThread *t = asanThreadRegistry().GetCurrent(); + if (!t) { + t = AsanThread::Create(parent_tid, 0, 0, stack); + asanThreadRegistry().RegisterThread(t); + t->Init(); + asanThreadRegistry().SetCurrent(t); + } +} + +// For use by only those functions that allocated the context via +// alloc_asan_context(). extern "C" void asan_dispatch_call_block_and_release(void *block) { - GET_STACK_TRACE_HERE(kStackTraceMax); + GET_STACK_TRACE_THREAD; asan_block_context_t *context = (asan_block_context_t*)block; if (flags()->verbosity >= 2) { Report("asan_dispatch_call_block_and_release(): " "context: %p, pthread_self: %p\n", block, pthread_self()); } - AsanThread *t = asanThreadRegistry().GetCurrent(); - if (!t) { - t = AsanThread::Create(context->parent_tid, 0, 0, &stack); - asanThreadRegistry().RegisterThread(t); - t->Init(); - asanThreadRegistry().SetCurrent(t); - } + asan_register_worker_thread(context->parent_tid, &stack); // Call the original dispatcher for the block. context->func(context->block); - asan_free(context, &stack); + asan_free(context, &stack, FROM_MALLOC); } } // namespace __asan @@ -270,7 +308,7 @@ using namespace __asan; // NOLINT // The caller retains control of the allocated context. extern "C" asan_block_context_t *alloc_asan_context(void *ctxt, dispatch_function_t func, - AsanStackTrace *stack) { + StackTrace *stack) { asan_block_context_t *asan_ctxt = (asan_block_context_t*) asan_malloc(sizeof(asan_block_context_t), stack); asan_ctxt->block = ctxt; @@ -279,37 +317,30 @@ asan_block_context_t *alloc_asan_context(void *ctxt, dispatch_function_t func, return asan_ctxt; } -// TODO(glider): can we reduce code duplication by introducing a macro? -INTERCEPTOR(void, dispatch_async_f, dispatch_queue_t dq, void *ctxt, - dispatch_function_t func) { - GET_STACK_TRACE_HERE(kStackTraceMax); - asan_block_context_t *asan_ctxt = alloc_asan_context(ctxt, func, &stack); - if (flags()->verbosity >= 2) { - Report("dispatch_async_f(): context: %p, pthread_self: %p\n", - asan_ctxt, pthread_self()); - PRINT_CURRENT_STACK(); +// Define interceptor for dispatch_*_f function with the three most common +// parameters: dispatch_queue_t, context, dispatch_function_t. +#define INTERCEPT_DISPATCH_X_F_3(dispatch_x_f) \ + INTERCEPTOR(void, dispatch_x_f, dispatch_queue_t dq, void *ctxt, \ + dispatch_function_t func) { \ + GET_STACK_TRACE_THREAD; \ + asan_block_context_t *asan_ctxt = alloc_asan_context(ctxt, func, &stack); \ + if (flags()->verbosity >= 2) { \ + Report(#dispatch_x_f "(): context: %p, pthread_self: %p\n", \ + asan_ctxt, pthread_self()); \ + PRINT_CURRENT_STACK(); \ + } \ + return REAL(dispatch_x_f)(dq, (void*)asan_ctxt, \ + asan_dispatch_call_block_and_release); \ } - return REAL(dispatch_async_f)(dq, (void*)asan_ctxt, - asan_dispatch_call_block_and_release); -} -INTERCEPTOR(void, dispatch_sync_f, dispatch_queue_t dq, void *ctxt, - dispatch_function_t func) { - GET_STACK_TRACE_HERE(kStackTraceMax); - asan_block_context_t *asan_ctxt = alloc_asan_context(ctxt, func, &stack); - if (flags()->verbosity >= 2) { - Report("dispatch_sync_f(): context: %p, pthread_self: %p\n", - asan_ctxt, pthread_self()); - PRINT_CURRENT_STACK(); - } - return REAL(dispatch_sync_f)(dq, (void*)asan_ctxt, - asan_dispatch_call_block_and_release); -} +INTERCEPT_DISPATCH_X_F_3(dispatch_async_f) +INTERCEPT_DISPATCH_X_F_3(dispatch_sync_f) +INTERCEPT_DISPATCH_X_F_3(dispatch_barrier_async_f) INTERCEPTOR(void, dispatch_after_f, dispatch_time_t when, dispatch_queue_t dq, void *ctxt, dispatch_function_t func) { - GET_STACK_TRACE_HERE(kStackTraceMax); + GET_STACK_TRACE_THREAD; asan_block_context_t *asan_ctxt = alloc_asan_context(ctxt, func, &stack); if (flags()->verbosity >= 2) { Report("dispatch_after_f: %p\n", asan_ctxt); @@ -319,23 +350,10 @@ INTERCEPTOR(void, dispatch_after_f, dispatch_time_t when, asan_dispatch_call_block_and_release); } -INTERCEPTOR(void, dispatch_barrier_async_f, dispatch_queue_t dq, void *ctxt, - dispatch_function_t func) { - GET_STACK_TRACE_HERE(kStackTraceMax); - asan_block_context_t *asan_ctxt = alloc_asan_context(ctxt, func, &stack); - if (flags()->verbosity >= 2) { - Report("dispatch_barrier_async_f(): context: %p, pthread_self: %p\n", - asan_ctxt, pthread_self()); - PRINT_CURRENT_STACK(); - } - REAL(dispatch_barrier_async_f)(dq, (void*)asan_ctxt, - asan_dispatch_call_block_and_release); -} - INTERCEPTOR(void, dispatch_group_async_f, dispatch_group_t group, dispatch_queue_t dq, void *ctxt, dispatch_function_t func) { - GET_STACK_TRACE_HERE(kStackTraceMax); + GET_STACK_TRACE_THREAD; asan_block_context_t *asan_ctxt = alloc_asan_context(ctxt, func, &stack); if (flags()->verbosity >= 2) { Report("dispatch_group_async_f(): context: %p, pthread_self: %p\n", @@ -346,43 +364,66 @@ INTERCEPTOR(void, dispatch_group_async_f, dispatch_group_t group, asan_dispatch_call_block_and_release); } -// The following stuff has been extremely helpful while looking for the -// unhandled functions that spawned jobs on Chromium shutdown. If the verbosity -// level is 2 or greater, we wrap pthread_workqueue_additem_np() in order to -// find the points of worker thread creation (each of such threads may be used -// to run several tasks, that's why this is not enough to support the whole -// libdispatch API. -extern "C" -void *wrap_workitem_func(void *arg) { - if (flags()->verbosity >= 2) { - Report("wrap_workitem_func: %p, pthread_self: %p\n", arg, pthread_self()); - } - asan_block_context_t *ctxt = (asan_block_context_t*)arg; - worker_t fn = (worker_t)(ctxt->func); - void *result = fn(ctxt->block); - GET_STACK_TRACE_HERE(kStackTraceMax); - asan_free(arg, &stack); - return result; +#if MAC_INTERPOSE_FUNCTIONS && !defined(MISSING_BLOCKS_SUPPORT) +// dispatch_async, dispatch_group_async and others tailcall the corresponding +// dispatch_*_f functions. When wrapping functions with mach_override, those +// dispatch_*_f are intercepted automatically. But with dylib interposition +// this does not work, because the calls within the same library are not +// interposed. +// Therefore we need to re-implement dispatch_async and friends. + +extern "C" { +// FIXME: consolidate these declarations with asan_intercepted_functions.h. +void dispatch_async(dispatch_queue_t dq, void(^work)(void)); +void dispatch_group_async(dispatch_group_t dg, dispatch_queue_t dq, + void(^work)(void)); +void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, + void(^work)(void)); +void dispatch_source_set_cancel_handler(dispatch_source_t ds, + void(^work)(void)); +void dispatch_source_set_event_handler(dispatch_source_t ds, void(^work)(void)); } -INTERCEPTOR(int, pthread_workqueue_additem_np, pthread_workqueue_t workq, - void *(*workitem_func)(void *), void * workitem_arg, - pthread_workitem_handle_t * itemhandlep, unsigned int *gencountp) { - GET_STACK_TRACE_HERE(kStackTraceMax); - asan_block_context_t *asan_ctxt = - (asan_block_context_t*) asan_malloc(sizeof(asan_block_context_t), &stack); - asan_ctxt->block = workitem_arg; - asan_ctxt->func = (dispatch_function_t)workitem_func; - asan_ctxt->parent_tid = asanThreadRegistry().GetCurrentTidOrInvalid(); - if (flags()->verbosity >= 2) { - Report("pthread_workqueue_additem_np: %p\n", asan_ctxt); - PRINT_CURRENT_STACK(); +#define GET_ASAN_BLOCK(work) \ + void (^asan_block)(void); \ + int parent_tid = asanThreadRegistry().GetCurrentTidOrInvalid(); \ + asan_block = ^(void) { \ + GET_STACK_TRACE_THREAD; \ + asan_register_worker_thread(parent_tid, &stack); \ + work(); \ } - return REAL(pthread_workqueue_additem_np)(workq, wrap_workitem_func, - asan_ctxt, itemhandlep, - gencountp); + +INTERCEPTOR(void, dispatch_async, + dispatch_queue_t dq, void(^work)(void)) { + GET_ASAN_BLOCK(work); + REAL(dispatch_async)(dq, asan_block); +} + +INTERCEPTOR(void, dispatch_group_async, + dispatch_group_t dg, dispatch_queue_t dq, void(^work)(void)) { + GET_ASAN_BLOCK(work); + REAL(dispatch_group_async)(dg, dq, asan_block); +} + +INTERCEPTOR(void, dispatch_after, + dispatch_time_t when, dispatch_queue_t queue, void(^work)(void)) { + GET_ASAN_BLOCK(work); + REAL(dispatch_after)(when, queue, asan_block); } +INTERCEPTOR(void, dispatch_source_set_cancel_handler, + dispatch_source_t ds, void(^work)(void)) { + GET_ASAN_BLOCK(work); + REAL(dispatch_source_set_cancel_handler)(ds, asan_block); +} + +INTERCEPTOR(void, dispatch_source_set_event_handler, + dispatch_source_t ds, void(^work)(void)) { + GET_ASAN_BLOCK(work); + REAL(dispatch_source_set_event_handler)(ds, asan_block); +} +#endif + // See http://opensource.apple.com/source/CF/CF-635.15/CFString.c int __CFStrIsConstant(CFStringRef str) { CFRuntimeBase *base = (CFRuntimeBase*)str; @@ -404,9 +445,7 @@ INTERCEPTOR(CFStringRef, CFStringCreateCopy, CFAllocatorRef alloc, DECLARE_REAL_AND_INTERCEPTOR(void, free, void *ptr) -extern "C" -void __CFInitialize(); -DECLARE_REAL_AND_INTERCEPTOR(void, __CFInitialize) +DECLARE_REAL_AND_INTERCEPTOR(void, __CFInitialize, void) namespace __asan { @@ -416,12 +455,6 @@ void InitializeMacInterceptors() { CHECK(INTERCEPT_FUNCTION(dispatch_after_f)); CHECK(INTERCEPT_FUNCTION(dispatch_barrier_async_f)); CHECK(INTERCEPT_FUNCTION(dispatch_group_async_f)); - // We don't need to intercept pthread_workqueue_additem_np() to support the - // libdispatch API, but it helps us to debug the unsupported functions. Let's - // intercept it only during verbose runs. - if (flags()->verbosity >= 2) { - CHECK(INTERCEPT_FUNCTION(pthread_workqueue_additem_np)); - } // Normally CFStringCreateCopy should not copy constant CF strings. // Replacing the default CFAllocator causes constant strings to be copied // rather than just returned, which leads to bugs in big applications like diff --git a/lib/asan/asan_mac.h b/lib/asan/asan_mac.h index 6c65765a804c..be913865c440 100644 --- a/lib/asan/asan_mac.h +++ b/lib/asan/asan_mac.h @@ -40,13 +40,17 @@ enum { MACOS_VERSION_UNKNOWN = 0, MACOS_VERSION_LEOPARD, MACOS_VERSION_SNOW_LEOPARD, - MACOS_VERSION_LION + MACOS_VERSION_LION, + MACOS_VERSION_MOUNTAIN_LION }; +// Used by asan_malloc_mac.cc and asan_mac.cc +extern "C" void __CFInitialize(); + namespace __asan { int GetMacosVersion(); -void ReplaceCFAllocator(); +void MaybeReplaceCFAllocator(); } // namespace __asan diff --git a/lib/asan/asan_malloc_linux.cc b/lib/asan/asan_malloc_linux.cc index 1046f4c843a8..b95cfe3149b7 100644 --- a/lib/asan/asan_malloc_linux.cc +++ b/lib/asan/asan_malloc_linux.cc @@ -19,8 +19,16 @@ #include "asan_interceptors.h" #include "asan_internal.h" #include "asan_stack.h" +#include "asan_thread_registry.h" +#include "sanitizer/asan_interface.h" + +#if ASAN_ANDROID +DECLARE_REAL_AND_INTERCEPTOR(void*, malloc, uptr size) +DECLARE_REAL_AND_INTERCEPTOR(void, free, void *ptr) +DECLARE_REAL_AND_INTERCEPTOR(void*, calloc, uptr nmemb, uptr size) +DECLARE_REAL_AND_INTERCEPTOR(void*, realloc, void *ptr, uptr size) +DECLARE_REAL_AND_INTERCEPTOR(void*, memalign, uptr boundary, uptr size) -#ifdef ANDROID struct MallocDebug { void* (*malloc)(uptr bytes); void (*free)(void* mem); @@ -30,7 +38,7 @@ struct MallocDebug { }; const MallocDebug asan_malloc_dispatch ALIGNED(32) = { - malloc, free, calloc, realloc, memalign + WRAP(malloc), WRAP(free), WRAP(calloc), WRAP(realloc), WRAP(memalign) }; extern "C" const MallocDebug* __libc_malloc_dispatch; @@ -53,17 +61,17 @@ void ReplaceSystemMalloc() { using namespace __asan; // NOLINT INTERCEPTOR(void, free, void *ptr) { - GET_STACK_TRACE_HERE_FOR_FREE(ptr); - asan_free(ptr, &stack); + GET_STACK_TRACE_FREE; + asan_free(ptr, &stack, FROM_MALLOC); } INTERCEPTOR(void, cfree, void *ptr) { - GET_STACK_TRACE_HERE_FOR_FREE(ptr); - asan_free(ptr, &stack); + GET_STACK_TRACE_FREE; + asan_free(ptr, &stack, FROM_MALLOC); } INTERCEPTOR(void*, malloc, uptr size) { - GET_STACK_TRACE_HERE_FOR_MALLOC; + GET_STACK_TRACE_MALLOC; return asan_malloc(size, &stack); } @@ -79,25 +87,25 @@ INTERCEPTOR(void*, calloc, uptr nmemb, uptr size) { CHECK(allocated < kCallocPoolSize); return mem; } - GET_STACK_TRACE_HERE_FOR_MALLOC; + GET_STACK_TRACE_MALLOC; return asan_calloc(nmemb, size, &stack); } INTERCEPTOR(void*, realloc, void *ptr, uptr size) { - GET_STACK_TRACE_HERE_FOR_MALLOC; + GET_STACK_TRACE_MALLOC; return asan_realloc(ptr, size, &stack); } INTERCEPTOR(void*, memalign, uptr boundary, uptr size) { - GET_STACK_TRACE_HERE_FOR_MALLOC; - return asan_memalign(boundary, size, &stack); + GET_STACK_TRACE_MALLOC; + return asan_memalign(boundary, size, &stack, FROM_MALLOC); } INTERCEPTOR(void*, __libc_memalign, uptr align, uptr s) ALIAS("memalign"); INTERCEPTOR(uptr, malloc_usable_size, void *ptr) { - GET_STACK_TRACE_HERE_FOR_MALLOC; + GET_STACK_TRACE_MALLOC; return asan_malloc_usable_size(ptr, &stack); } @@ -120,19 +128,23 @@ INTERCEPTOR(int, mallopt, int cmd, int value) { } INTERCEPTOR(int, posix_memalign, void **memptr, uptr alignment, uptr size) { - GET_STACK_TRACE_HERE_FOR_MALLOC; + GET_STACK_TRACE_MALLOC; // Printf("posix_memalign: %zx %zu\n", alignment, size); return asan_posix_memalign(memptr, alignment, size, &stack); } INTERCEPTOR(void*, valloc, uptr size) { - GET_STACK_TRACE_HERE_FOR_MALLOC; + GET_STACK_TRACE_MALLOC; return asan_valloc(size, &stack); } INTERCEPTOR(void*, pvalloc, uptr size) { - GET_STACK_TRACE_HERE_FOR_MALLOC; + GET_STACK_TRACE_MALLOC; return asan_pvalloc(size, &stack); } +INTERCEPTOR(void, malloc_stats, void) { + __asan_print_accumulated_stats(); +} + #endif // __linux__ diff --git a/lib/asan/asan_malloc_mac.cc b/lib/asan/asan_malloc_mac.cc index 1a6c84052a0a..545ede2debe7 100644 --- a/lib/asan/asan_malloc_mac.cc +++ b/lib/asan/asan_malloc_mac.cc @@ -1,4 +1,4 @@ -//===-- asan_rtl.cc -------------------------------------------------------===// +//===-- asan_malloc_mac.cc ------------------------------------------------===// // // The LLVM Compiler Infrastructure // @@ -18,13 +18,15 @@ #include <CoreFoundation/CFBase.h> #include <dlfcn.h> #include <malloc/malloc.h> -#include <setjmp.h> #include "asan_allocator.h" #include "asan_interceptors.h" #include "asan_internal.h" #include "asan_mac.h" +#include "asan_report.h" #include "asan_stack.h" +#include "asan_stats.h" +#include "asan_thread_registry.h" // Similar code is used in Google Perftools, // http://code.google.com/p/google-perftools. @@ -38,6 +40,30 @@ static malloc_zone_t *system_purgeable_zone = 0; static malloc_zone_t asan_zone; CFAllocatorRef cf_asan = 0; +// _CFRuntimeCreateInstance() checks whether the supplied allocator is +// kCFAllocatorSystemDefault and, if it is not, stores the allocator reference +// at the beginning of the allocated memory and returns the pointer to the +// allocated memory plus sizeof(CFAllocatorRef). See +// http://www.opensource.apple.com/source/CF/CF-635.21/CFRuntime.c +// Pointers returned by _CFRuntimeCreateInstance() can then be passed directly +// to free() or CFAllocatorDeallocate(), which leads to false invalid free +// reports. +// The corresponding rdar bug is http://openradar.appspot.com/radar?id=1796404. +void* ALWAYS_INLINE get_saved_cfallocator_ref(void *ptr) { + if (flags()->replace_cfallocator) { + // Make sure we're not hitting the previous page. This may be incorrect + // if ASan's malloc returns an address ending with 0xFF8, which will be + // then padded to a page boundary with a CFAllocatorRef. + uptr arith_ptr = (uptr)ptr; + if ((arith_ptr & 0xFFF) > sizeof(CFAllocatorRef)) { + CFAllocatorRef *saved = + (CFAllocatorRef*)(arith_ptr - sizeof(CFAllocatorRef)); + if ((*saved == cf_asan) && asan_mz_size(saved)) ptr = (void*)saved; + } + } + return ptr; +} + // The free() implementation provided by OS X calls malloc_zone_from_ptr() // to find the owner of |ptr|. If the result is 0, an invalid free() is // reported. Our implementation falls back to asan_free() in this case @@ -65,26 +91,12 @@ INTERCEPTOR(void, free, void *ptr) { malloc_zone_free(zone, ptr); #endif } else { - if (flags()->replace_cfallocator) { - // Make sure we're not hitting the previous page. This may be incorrect - // if ASan's malloc returns an address ending with 0xFF8, which will be - // then padded to a page boundary with a CFAllocatorRef. - uptr arith_ptr = (uptr)ptr; - if ((arith_ptr & 0xFFF) > sizeof(CFAllocatorRef)) { - CFAllocatorRef *saved = - (CFAllocatorRef*)(arith_ptr - sizeof(CFAllocatorRef)); - if ((*saved == cf_asan) && asan_mz_size(saved)) ptr = (void*)saved; - } - } - GET_STACK_TRACE_HERE_FOR_FREE(ptr); - asan_free(ptr, &stack); + if (!asan_mz_size(ptr)) ptr = get_saved_cfallocator_ref(ptr); + GET_STACK_TRACE_FREE; + asan_free(ptr, &stack, FROM_MALLOC); } } -namespace __asan { - void ReplaceCFAllocator(); -} - // We can't always replace the default CFAllocator with cf_asan right in // ReplaceSystemMalloc(), because it is sometimes called before // __CFInitialize(), when the default allocator is invalid and replacing it may @@ -94,11 +106,15 @@ namespace __asan { // // See http://code.google.com/p/address-sanitizer/issues/detail?id=87 // and http://opensource.apple.com/source/CF/CF-550.43/CFRuntime.c -INTERCEPTOR(void, __CFInitialize) { +INTERCEPTOR(void, __CFInitialize, void) { + // If the runtime is built as dynamic library, __CFInitialize wrapper may be + // called before __asan_init. +#if !MAC_INTERPOSE_FUNCTIONS CHECK(flags()->replace_cfallocator); CHECK(asan_inited); +#endif REAL(__CFInitialize)(); - if (!cf_asan) ReplaceCFAllocator(); + if (!cf_asan && asan_inited) MaybeReplaceCFAllocator(); } namespace { @@ -114,7 +130,7 @@ void *mz_malloc(malloc_zone_t *zone, size_t size) { CHECK(system_malloc_zone); return malloc_zone_malloc(system_malloc_zone, size); } - GET_STACK_TRACE_HERE_FOR_MALLOC; + GET_STACK_TRACE_MALLOC; return asan_malloc(size, &stack); } @@ -123,7 +139,7 @@ void *cf_malloc(CFIndex size, CFOptionFlags hint, void *info) { CHECK(system_malloc_zone); return malloc_zone_malloc(system_malloc_zone, size); } - GET_STACK_TRACE_HERE_FOR_MALLOC; + GET_STACK_TRACE_MALLOC; return asan_malloc(size, &stack); } @@ -139,7 +155,7 @@ void *mz_calloc(malloc_zone_t *zone, size_t nmemb, size_t size) { CHECK(allocated < kCallocPoolSize); return mem; } - GET_STACK_TRACE_HERE_FOR_MALLOC; + GET_STACK_TRACE_MALLOC; return asan_calloc(nmemb, size, &stack); } @@ -148,39 +164,41 @@ void *mz_valloc(malloc_zone_t *zone, size_t size) { CHECK(system_malloc_zone); return malloc_zone_valloc(system_malloc_zone, size); } - GET_STACK_TRACE_HERE_FOR_MALLOC; - return asan_memalign(kPageSize, size, &stack); + GET_STACK_TRACE_MALLOC; + return asan_memalign(GetPageSizeCached(), size, &stack, FROM_MALLOC); } -void print_zone_for_ptr(void *ptr) { - malloc_zone_t *orig_zone = malloc_zone_from_ptr(ptr); - if (orig_zone) { - if (orig_zone->zone_name) { - AsanPrintf("malloc_zone_from_ptr(%p) = %p, which is %s\n", - ptr, orig_zone, orig_zone->zone_name); - } else { - AsanPrintf("malloc_zone_from_ptr(%p) = %p, which doesn't have a name\n", - ptr, orig_zone); - } - } else { - AsanPrintf("malloc_zone_from_ptr(%p) = 0\n", ptr); - } -} +#define GET_ZONE_FOR_PTR(ptr) \ + malloc_zone_t *zone_ptr = malloc_zone_from_ptr(ptr); \ + const char *zone_name = (zone_ptr == 0) ? 0 : zone_ptr->zone_name void ALWAYS_INLINE free_common(void *context, void *ptr) { if (!ptr) return; - if (!flags()->mac_ignore_invalid_free || asan_mz_size(ptr)) { - GET_STACK_TRACE_HERE_FOR_FREE(ptr); - asan_free(ptr, &stack); + if (asan_mz_size(ptr)) { + GET_STACK_TRACE_FREE; + asan_free(ptr, &stack, FROM_MALLOC); } else { - // Let us just leak this memory for now. - AsanPrintf("free_common(%p) -- attempting to free unallocated memory.\n" - "AddressSanitizer is ignoring this error on Mac OS now.\n", - ptr); - print_zone_for_ptr(ptr); - GET_STACK_TRACE_HERE_FOR_FREE(ptr); - stack.PrintStack(); - return; + // If the pointer does not belong to any of the zones, use one of the + // fallback methods to free memory. + malloc_zone_t *zone_ptr = malloc_zone_from_ptr(ptr); + if (zone_ptr == system_purgeable_zone) { + // allocations from malloc_default_purgeable_zone() done before + // __asan_init() may be occasionally freed via free_common(). + // see http://code.google.com/p/address-sanitizer/issues/detail?id=99. + malloc_zone_free(zone_ptr, ptr); + } else { + // If the memory chunk pointer was moved to store additional + // CFAllocatorRef, fix it back. + ptr = get_saved_cfallocator_ref(ptr); + GET_STACK_TRACE_FREE; + if (!flags()->mac_ignore_invalid_free) { + asan_free(ptr, &stack, FROM_MALLOC); + } else { + GET_ZONE_FOR_PTR(ptr); + WarnMacFreeUnallocated((uptr)ptr, (uptr)zone_ptr, zone_name, &stack); + return; + } + } } } @@ -195,55 +213,45 @@ void cf_free(void *ptr, void *info) { void *mz_realloc(malloc_zone_t *zone, void *ptr, size_t size) { if (!ptr) { - GET_STACK_TRACE_HERE_FOR_MALLOC; + GET_STACK_TRACE_MALLOC; return asan_malloc(size, &stack); } else { if (asan_mz_size(ptr)) { - GET_STACK_TRACE_HERE_FOR_MALLOC; + GET_STACK_TRACE_MALLOC; return asan_realloc(ptr, size, &stack); } else { // We can't recover from reallocating an unknown address, because // this would require reading at most |size| bytes from // potentially unaccessible memory. - AsanPrintf("mz_realloc(%p) -- attempting to realloc unallocated memory.\n" - "This is an unrecoverable problem, exiting now.\n", - ptr); - print_zone_for_ptr(ptr); - GET_STACK_TRACE_HERE_FOR_FREE(ptr); - stack.PrintStack(); - ShowStatsAndAbort(); - return 0; // unreachable + GET_STACK_TRACE_FREE; + GET_ZONE_FOR_PTR(ptr); + ReportMacMzReallocUnknown((uptr)ptr, (uptr)zone_ptr, zone_name, &stack); } } } void *cf_realloc(void *ptr, CFIndex size, CFOptionFlags hint, void *info) { if (!ptr) { - GET_STACK_TRACE_HERE_FOR_MALLOC; + GET_STACK_TRACE_MALLOC; return asan_malloc(size, &stack); } else { if (asan_mz_size(ptr)) { - GET_STACK_TRACE_HERE_FOR_MALLOC; + GET_STACK_TRACE_MALLOC; return asan_realloc(ptr, size, &stack); } else { // We can't recover from reallocating an unknown address, because // this would require reading at most |size| bytes from // potentially unaccessible memory. - AsanPrintf("cf_realloc(%p) -- attempting to realloc unallocated memory.\n" - "This is an unrecoverable problem, exiting now.\n", - ptr); - print_zone_for_ptr(ptr); - GET_STACK_TRACE_HERE_FOR_FREE(ptr); - stack.PrintStack(); - ShowStatsAndAbort(); - return 0; // unreachable + GET_STACK_TRACE_FREE; + GET_ZONE_FOR_PTR(ptr); + ReportMacCfReallocUnknown((uptr)ptr, (uptr)zone_ptr, zone_name, &stack); } } } void mz_destroy(malloc_zone_t* zone) { // A no-op -- we will not be destroyed! - AsanPrintf("mz_destroy() called -- ignoring\n"); + Printf("mz_destroy() called -- ignoring\n"); } // from AvailabilityMacros.h #if defined(MAC_OS_X_VERSION_10_6) && \ @@ -253,8 +261,8 @@ void *mz_memalign(malloc_zone_t *zone, size_t align, size_t size) { CHECK(system_malloc_zone); return malloc_zone_memalign(system_malloc_zone, align, size); } - GET_STACK_TRACE_HERE_FOR_MALLOC; - return asan_memalign(align, size, &stack); + GET_STACK_TRACE_MALLOC; + return asan_memalign(align, size, &stack, FROM_MALLOC); } // This function is currently unused, and we build with -Werror. @@ -266,7 +274,6 @@ void mz_free_definite_size(malloc_zone_t* zone, void *ptr, size_t size) { #endif #endif -// malloc_introspection callbacks. I'm not clear on what all of these do. kern_return_t mi_enumerator(task_t task, void *, unsigned type_mask, vm_address_t zone_address, memory_reader_t reader, @@ -282,12 +289,10 @@ size_t mi_good_size(malloc_zone_t *zone, size_t size) { boolean_t mi_check(malloc_zone_t *zone) { UNIMPLEMENTED(); - return true; } void mi_print(malloc_zone_t *zone, boolean_t verbose) { UNIMPLEMENTED(); - return; } void mi_log(malloc_zone_t *zone, void *address) { @@ -302,17 +307,12 @@ void mi_force_unlock(malloc_zone_t *zone) { asan_mz_force_unlock(); } -// This function is currently unused, and we build with -Werror. -#if 0 void mi_statistics(malloc_zone_t *zone, malloc_statistics_t *stats) { - // TODO(csilvers): figure out how to fill these out - // TODO(glider): port this from tcmalloc when ready. - stats->blocks_in_use = 0; - stats->size_in_use = 0; - stats->max_size_in_use = 0; - stats->size_allocated = 0; + AsanMallocStats malloc_stats; + asanThreadRegistry().FillMallocStatistics(&malloc_stats); + CHECK(sizeof(malloc_statistics_t) == sizeof(AsanMallocStats)); + internal_memcpy(stats, &malloc_stats, sizeof(malloc_statistics_t)); } -#endif #if defined(MAC_OS_X_VERSION_10_6) && \ MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 @@ -327,7 +327,7 @@ boolean_t mi_zone_locked(malloc_zone_t *zone) { extern int __CFRuntimeClassTableSize; namespace __asan { -void ReplaceCFAllocator() { +void MaybeReplaceCFAllocator() { static CFAllocatorContext asan_context = { /*version*/ 0, /*info*/ &asan_zone, /*retain*/ 0, /*release*/ 0, @@ -338,7 +338,7 @@ void ReplaceCFAllocator() { /*preferredSize*/ 0 }; if (!cf_asan) cf_asan = CFAllocatorCreate(kCFAllocatorUseContext, &asan_context); - if (CFAllocatorGetDefault() != cf_asan) + if (flags()->replace_cfallocator && CFAllocatorGetDefault() != cf_asan) CFAllocatorSetDefault(cf_asan); } @@ -354,6 +354,7 @@ void ReplaceSystemMalloc() { asan_introspection.log = &mi_log; asan_introspection.force_lock = &mi_force_lock; asan_introspection.force_unlock = &mi_force_unlock; + asan_introspection.statistics = &mi_statistics; internal_memset(&asan_zone, 0, sizeof(malloc_zone_t)); @@ -405,16 +406,14 @@ void ReplaceSystemMalloc() { // Make sure the default allocator was replaced. CHECK(malloc_default_zone() == &asan_zone); - if (flags()->replace_cfallocator) { - // If __CFInitialize() hasn't been called yet, cf_asan will be created and - // installed as the default allocator after __CFInitialize() finishes (see - // the interceptor for __CFInitialize() above). Otherwise install cf_asan - // right now. On both Snow Leopard and Lion __CFInitialize() calls - // __CFAllocatorInitialize(), which initializes the _base._cfisa field of - // the default allocators we check here. - if (((CFRuntimeBase*)kCFAllocatorSystemDefault)->_cfisa) { - ReplaceCFAllocator(); - } + // If __CFInitialize() hasn't been called yet, cf_asan will be created and + // installed as the default allocator after __CFInitialize() finishes (see + // the interceptor for __CFInitialize() above). Otherwise install cf_asan + // right now. On both Snow Leopard and Lion __CFInitialize() calls + // __CFAllocatorInitialize(), which initializes the _base._cfisa field of + // the default allocators we check here. + if (((CFRuntimeBase*)kCFAllocatorSystemDefault)->_cfisa) { + MaybeReplaceCFAllocator(); } } } // namespace __asan diff --git a/lib/asan/asan_malloc_win.cc b/lib/asan/asan_malloc_win.cc index 6c00e77caae8..9fcfea56384f 100644 --- a/lib/asan/asan_malloc_win.cc +++ b/lib/asan/asan_malloc_win.cc @@ -17,9 +17,10 @@ #include "asan_interceptors.h" #include "asan_internal.h" #include "asan_stack.h" - #include "interception/interception.h" +#include <stddef.h> + // ---------------------- Replacement functions ---------------- {{{1 using namespace __asan; // NOLINT @@ -30,8 +31,8 @@ using namespace __asan; // NOLINT extern "C" { void free(void *ptr) { - GET_STACK_TRACE_HERE_FOR_FREE(ptr); - return asan_free(ptr, &stack); + GET_STACK_TRACE_FREE; + return asan_free(ptr, &stack, FROM_MALLOC); } void _free_dbg(void* ptr, int) { @@ -43,7 +44,7 @@ void cfree(void *ptr) { } void *malloc(size_t size) { - GET_STACK_TRACE_HERE_FOR_MALLOC; + GET_STACK_TRACE_MALLOC; return asan_malloc(size, &stack); } @@ -52,7 +53,7 @@ void* _malloc_dbg(size_t size, int , const char*, int) { } void *calloc(size_t nmemb, size_t size) { - GET_STACK_TRACE_HERE_FOR_MALLOC; + GET_STACK_TRACE_MALLOC; return asan_calloc(nmemb, size, &stack); } @@ -65,7 +66,7 @@ void *_calloc_impl(size_t nmemb, size_t size, int *errno_tmp) { } void *realloc(void *ptr, size_t size) { - GET_STACK_TRACE_HERE_FOR_MALLOC; + GET_STACK_TRACE_MALLOC; return asan_realloc(ptr, size, &stack); } @@ -84,7 +85,7 @@ void* _recalloc(void* p, size_t n, size_t elem_size) { } size_t _msize(void *ptr) { - GET_STACK_TRACE_HERE_FOR_MALLOC; + GET_STACK_TRACE_MALLOC; return asan_malloc_usable_size(ptr, &stack); } diff --git a/lib/asan/asan_mapping.h b/lib/asan/asan_mapping.h index 8e0c6ec5db20..5e3067031f4a 100644 --- a/lib/asan/asan_mapping.h +++ b/lib/asan/asan_mapping.h @@ -20,20 +20,24 @@ // http://code.google.com/p/address-sanitizer/wiki/AddressSanitizerAlgorithm #if ASAN_FLEXIBLE_MAPPING_AND_OFFSET == 1 -extern __attribute__((visibility("default"))) uptr __asan_mapping_scale; -extern __attribute__((visibility("default"))) uptr __asan_mapping_offset; +extern SANITIZER_INTERFACE_ATTRIBUTE uptr __asan_mapping_scale; +extern SANITIZER_INTERFACE_ATTRIBUTE uptr __asan_mapping_offset; # define SHADOW_SCALE (__asan_mapping_scale) # define SHADOW_OFFSET (__asan_mapping_offset) #else -# ifdef ANDROID +# if ASAN_ANDROID # define SHADOW_SCALE (3) # define SHADOW_OFFSET (0) # else # define SHADOW_SCALE (3) -# if __WORDSIZE == 32 +# if SANITIZER_WORDSIZE == 32 # define SHADOW_OFFSET (1 << 29) # else -# define SHADOW_OFFSET (1ULL << 44) +# if defined(__powerpc64__) +# define SHADOW_OFFSET (1ULL << 41) +# else +# define SHADOW_OFFSET (1ULL << 44) +# endif # endif # endif #endif // ASAN_FLEXIBLE_MAPPING_AND_OFFSET @@ -42,11 +46,15 @@ extern __attribute__((visibility("default"))) uptr __asan_mapping_offset; #define MEM_TO_SHADOW(mem) (((mem) >> SHADOW_SCALE) | (SHADOW_OFFSET)) #define SHADOW_TO_MEM(shadow) (((shadow) - SHADOW_OFFSET) << SHADOW_SCALE) -#if __WORDSIZE == 64 +#if SANITIZER_WORDSIZE == 64 +# if defined(__powerpc64__) + static const uptr kHighMemEnd = 0x00000fffffffffffUL; +# else static const uptr kHighMemEnd = 0x00007fffffffffffUL; -#else // __WORDSIZE == 32 +# endif +#else // SANITIZER_WORDSIZE == 32 static const uptr kHighMemEnd = 0xffffffff; -#endif // __WORDSIZE +#endif // SANITIZER_WORDSIZE #define kLowMemBeg 0 @@ -60,7 +68,12 @@ extern __attribute__((visibility("default"))) uptr __asan_mapping_offset; #define kHighShadowBeg MEM_TO_SHADOW(kHighMemBeg) #define kHighShadowEnd MEM_TO_SHADOW(kHighMemEnd) -#define kShadowGapBeg (kLowShadowEnd ? kLowShadowEnd + 1 : 16 * kPageSize) +// With the zero shadow base we can not actually map pages starting from 0. +// This constant is somewhat arbitrary. +#define kZeroBaseShadowStart (1 << 18) + +#define kShadowGapBeg (kLowShadowEnd ? kLowShadowEnd + 1 \ + : kZeroBaseShadowStart) #define kShadowGapEnd (kHighShadowBeg - 1) #define kGlobalAndStackRedzone \ diff --git a/lib/asan/asan_new_delete.cc b/lib/asan/asan_new_delete.cc index 4a7275842f08..5d1f23c542e6 100644 --- a/lib/asan/asan_new_delete.cc +++ b/lib/asan/asan_new_delete.cc @@ -17,7 +17,6 @@ #include "asan_stack.h" #include <stddef.h> -#include <new> namespace __asan { // This function is a no-op. We need it to make sure that object file @@ -28,29 +27,42 @@ void ReplaceOperatorsNewAndDelete() { } using namespace __asan; // NOLINT -#define OPERATOR_NEW_BODY \ - GET_STACK_TRACE_HERE_FOR_MALLOC;\ - return asan_memalign(0, size, &stack); - -#ifdef ANDROID -void *operator new(size_t size) { OPERATOR_NEW_BODY; } -void *operator new[](size_t size) { OPERATOR_NEW_BODY; } -#else -void *operator new(size_t size) throw(std::bad_alloc) { OPERATOR_NEW_BODY; } -void *operator new[](size_t size) throw(std::bad_alloc) { OPERATOR_NEW_BODY; } -void *operator new(size_t size, std::nothrow_t const&) throw() -{ OPERATOR_NEW_BODY; } -void *operator new[](size_t size, std::nothrow_t const&) throw() -{ OPERATOR_NEW_BODY; } -#endif +// On Android new() goes through malloc interceptors. +#if !ASAN_ANDROID + +// Fake std::nothrow_t to avoid including <new>. +namespace std { +struct nothrow_t {}; +} // namespace std + +#define OPERATOR_NEW_BODY(type) \ + GET_STACK_TRACE_MALLOC;\ + return asan_memalign(0, size, &stack, type); -#define OPERATOR_DELETE_BODY \ - GET_STACK_TRACE_HERE_FOR_FREE(ptr);\ - asan_free(ptr, &stack); +INTERCEPTOR_ATTRIBUTE +void *operator new(size_t size) { OPERATOR_NEW_BODY(FROM_NEW); } +INTERCEPTOR_ATTRIBUTE +void *operator new[](size_t size) { OPERATOR_NEW_BODY(FROM_NEW_BR); } +INTERCEPTOR_ATTRIBUTE +void *operator new(size_t size, std::nothrow_t const&) +{ OPERATOR_NEW_BODY(FROM_NEW); } +INTERCEPTOR_ATTRIBUTE +void *operator new[](size_t size, std::nothrow_t const&) +{ OPERATOR_NEW_BODY(FROM_NEW_BR); } -void operator delete(void *ptr) throw() { OPERATOR_DELETE_BODY; } -void operator delete[](void *ptr) throw() { OPERATOR_DELETE_BODY; } -void operator delete(void *ptr, std::nothrow_t const&) throw() -{ OPERATOR_DELETE_BODY; } -void operator delete[](void *ptr, std::nothrow_t const&) throw() -{ OPERATOR_DELETE_BODY; } +#define OPERATOR_DELETE_BODY(type) \ + GET_STACK_TRACE_FREE;\ + asan_free(ptr, &stack, type); + +INTERCEPTOR_ATTRIBUTE +void operator delete(void *ptr) { OPERATOR_DELETE_BODY(FROM_NEW); } +INTERCEPTOR_ATTRIBUTE +void operator delete[](void *ptr) { OPERATOR_DELETE_BODY(FROM_NEW_BR); } +INTERCEPTOR_ATTRIBUTE +void operator delete(void *ptr, std::nothrow_t const&) +{ OPERATOR_DELETE_BODY(FROM_NEW); } +INTERCEPTOR_ATTRIBUTE +void operator delete[](void *ptr, std::nothrow_t const&) +{ OPERATOR_DELETE_BODY(FROM_NEW_BR); } + +#endif diff --git a/lib/asan/asan_poisoning.cc b/lib/asan/asan_poisoning.cc index 3b9d9f6795ca..dc5749243569 100644 --- a/lib/asan/asan_poisoning.cc +++ b/lib/asan/asan_poisoning.cc @@ -13,17 +13,19 @@ //===----------------------------------------------------------------------===// #include "asan_interceptors.h" -#include "asan_interface.h" #include "asan_internal.h" #include "asan_mapping.h" +#include "sanitizer/asan_interface.h" +#include "sanitizer_common/sanitizer_libc.h" namespace __asan { void PoisonShadow(uptr addr, uptr size, u8 value) { + if (!flags()->poison_heap) return; CHECK(AddrIsAlignedByGranularity(addr)); CHECK(AddrIsAlignedByGranularity(addr + size)); uptr shadow_beg = MemToShadow(addr); - uptr shadow_end = MemToShadow(addr + size); + uptr shadow_end = MemToShadow(addr + size - SHADOW_GRANULARITY) + 1; CHECK(REAL(memset) != 0); REAL(memset)((void*)shadow_beg, value, shadow_end - shadow_beg); } @@ -32,6 +34,7 @@ void PoisonShadowPartialRightRedzone(uptr addr, uptr size, uptr redzone_size, u8 value) { + if (!flags()->poison_heap) return; CHECK(AddrIsAlignedByGranularity(addr)); u8 *shadow = (u8*)MemToShadow(addr); for (uptr i = 0; i < redzone_size; @@ -151,3 +154,67 @@ void __asan_unpoison_memory_region(void const volatile *addr, uptr size) { bool __asan_address_is_poisoned(void const volatile *addr) { return __asan::AddressIsPoisoned((uptr)addr); } + +uptr __asan_region_is_poisoned(uptr beg, uptr size) { + if (!size) return 0; + uptr end = beg + size; + if (!AddrIsInMem(beg)) return beg; + if (!AddrIsInMem(end)) return end; + uptr aligned_b = RoundUpTo(beg, SHADOW_GRANULARITY); + uptr aligned_e = RoundDownTo(end, SHADOW_GRANULARITY); + uptr shadow_beg = MemToShadow(aligned_b); + uptr shadow_end = MemToShadow(aligned_e); + // First check the first and the last application bytes, + // then check the SHADOW_GRANULARITY-aligned region by calling + // mem_is_zero on the corresponding shadow. + if (!__asan::AddressIsPoisoned(beg) && + !__asan::AddressIsPoisoned(end - 1) && + (shadow_end <= shadow_beg || + __sanitizer::mem_is_zero((const char *)shadow_beg, + shadow_end - shadow_beg))) + return 0; + // The fast check failed, so we have a poisoned byte somewhere. + // Find it slowly. + for (; beg < end; beg++) + if (__asan::AddressIsPoisoned(beg)) + return beg; + UNREACHABLE("mem_is_zero returned false, but poisoned byte was not found"); + return 0; +} + +// This is a simplified version of __asan_(un)poison_memory_region, which +// assumes that left border of region to be poisoned is properly aligned. +static void PoisonAlignedStackMemory(uptr addr, uptr size, bool do_poison) { + if (size == 0) return; + uptr aligned_size = size & ~(SHADOW_GRANULARITY - 1); + PoisonShadow(addr, aligned_size, + do_poison ? kAsanStackUseAfterScopeMagic : 0); + if (size == aligned_size) + return; + s8 end_offset = (s8)(size - aligned_size); + s8* shadow_end = (s8*)MemToShadow(addr + aligned_size); + s8 end_value = *shadow_end; + if (do_poison) { + // If possible, mark all the bytes mapping to last shadow byte as + // unaddressable. + if (end_value > 0 && end_value <= end_offset) + *shadow_end = (s8)kAsanStackUseAfterScopeMagic; + } else { + // If necessary, mark few first bytes mapping to last shadow byte + // as addressable + if (end_value != 0) + *shadow_end = Max(end_value, end_offset); + } +} + +void __asan_poison_stack_memory(uptr addr, uptr size) { + if (flags()->verbosity > 0) + Report("poisoning: %p %zx\n", (void*)addr, size); + PoisonAlignedStackMemory(addr, size, true); +} + +void __asan_unpoison_stack_memory(uptr addr, uptr size) { + if (flags()->verbosity > 0) + Report("unpoisoning: %p %zx\n", (void*)addr, size); + PoisonAlignedStackMemory(addr, size, false); +} diff --git a/lib/asan/asan_posix.cc b/lib/asan/asan_posix.cc index 061bb193034d..ceaf120fc803 100644 --- a/lib/asan/asan_posix.cc +++ b/lib/asan/asan_posix.cc @@ -16,6 +16,7 @@ #include "asan_internal.h" #include "asan_interceptors.h" #include "asan_mapping.h" +#include "asan_report.h" #include "asan_stack.h" #include "asan_thread_registry.h" #include "sanitizer_common/sanitizer_libc.h" @@ -53,14 +54,7 @@ static void ASAN_OnSIGSEGV(int, siginfo_t *siginfo, void *context) { if (13 != internal_write(2, "ASAN:SIGSEGV\n", 13)) Die(); uptr pc, sp, bp; GetPcSpBp(context, &pc, &sp, &bp); - AsanReport("ERROR: AddressSanitizer crashed on unknown address %p" - " (pc %p sp %p bp %p T%d)\n", - (void*)addr, (void*)pc, (void*)sp, (void*)bp, - asanThreadRegistry().GetCurrentTidOrInvalid()); - AsanPrintf("AddressSanitizer can not provide additional info. ABORTING\n"); - GET_STACK_TRACE_WITH_PC_AND_BP(kStackTraceMax, pc, bp); - stack.PrintStack(); - ShowStatsAndAbort(); + ReportSIGSEGV(pc, sp, bp, addr); } void SetAlternateSignalStack() { diff --git a/lib/asan/asan_printf.cc b/lib/asan/asan_printf.cc deleted file mode 100644 index e1304f0fbe3f..000000000000 --- a/lib/asan/asan_printf.cc +++ /dev/null @@ -1,59 +0,0 @@ -//===-- asan_printf.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. -// -// Internal printf function, used inside ASan run-time library. -// We can't use libc printf because we intercept some of the functions used -// inside it. -//===----------------------------------------------------------------------===// - -#include "asan_internal.h" -#include "asan_interceptors.h" -#include "sanitizer_common/sanitizer_libc.h" -#include "sanitizer_common/sanitizer_common.h" - -#include <stdarg.h> -#include <stdio.h> - -namespace __sanitizer { -int VSNPrintf(char *buff, int buff_length, const char *format, va_list args); -} // namespace __sanitizer - -namespace __asan { - -void AsanPrintf(const char *format, ...) { - const int kLen = 1024 * 4; - char buffer[kLen]; - va_list args; - va_start(args, format); - int needed_length = VSNPrintf(buffer, kLen, format, args); - va_end(args); - RAW_CHECK_MSG(needed_length < kLen, "Buffer in Printf is too short!\n"); - RawWrite(buffer); - AppendToErrorMessageBuffer(buffer); -} - -// Like AsanPrintf, but prints the current PID before the output string. -void AsanReport(const char *format, ...) { - const int kLen = 1024 * 4; - char buffer[kLen]; - int needed_length = internal_snprintf(buffer, kLen, "==%d== ", GetPid()); - RAW_CHECK_MSG(needed_length < kLen, "Buffer in Report is too short!\n"); - va_list args; - va_start(args, format); - needed_length += VSNPrintf(buffer + needed_length, kLen - needed_length, - format, args); - va_end(args); - RAW_CHECK_MSG(needed_length < kLen, "Buffer in Report is too short!\n"); - RawWrite(buffer); - AppendToErrorMessageBuffer(buffer); -} - -} // namespace __asan diff --git a/lib/asan/asan_report.cc b/lib/asan/asan_report.cc new file mode 100644 index 000000000000..35ab9cabde67 --- /dev/null +++ b/lib/asan/asan_report.cc @@ -0,0 +1,681 @@ +//===-- asan_report.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. +// +// This file contains error reporting code. +//===----------------------------------------------------------------------===// +#include "asan_flags.h" +#include "asan_internal.h" +#include "asan_mapping.h" +#include "asan_report.h" +#include "asan_stack.h" +#include "asan_thread.h" +#include "asan_thread_registry.h" +#include "sanitizer_common/sanitizer_common.h" +#include "sanitizer_common/sanitizer_report_decorator.h" +#include "sanitizer_common/sanitizer_symbolizer.h" + +namespace __asan { + +// -------------------- User-specified callbacks ----------------- {{{1 +static void (*error_report_callback)(const char*); +static char *error_message_buffer = 0; +static uptr error_message_buffer_pos = 0; +static uptr error_message_buffer_size = 0; + +void AppendToErrorMessageBuffer(const char *buffer) { + if (error_message_buffer) { + uptr length = internal_strlen(buffer); + CHECK_GE(error_message_buffer_size, error_message_buffer_pos); + uptr remaining = error_message_buffer_size - error_message_buffer_pos; + internal_strncpy(error_message_buffer + error_message_buffer_pos, + buffer, remaining); + error_message_buffer[error_message_buffer_size - 1] = '\0'; + // FIXME: reallocate the buffer instead of truncating the message. + error_message_buffer_pos += remaining > length ? length : remaining; + } +} + +// ---------------------- Decorator ------------------------------ {{{1 +bool PrintsToTtyCached() { + static int cached = 0; + static bool prints_to_tty; + if (!cached) { // Ok wrt threads since we are printing only from one thread. + prints_to_tty = PrintsToTty(); + cached = 1; + } + return prints_to_tty; +} +class Decorator: private __sanitizer::AnsiColorDecorator { + public: + Decorator() : __sanitizer::AnsiColorDecorator(PrintsToTtyCached()) { } + const char *Warning() { return Red(); } + const char *EndWarning() { return Default(); } + const char *Access() { return Blue(); } + const char *EndAccess() { return Default(); } + const char *Location() { return Green(); } + const char *EndLocation() { return Default(); } + const char *Allocation() { return Magenta(); } + const char *EndAllocation() { return Default(); } + + const char *ShadowByte(u8 byte) { + switch (byte) { + case kAsanHeapLeftRedzoneMagic: + case kAsanHeapRightRedzoneMagic: + return Red(); + case kAsanHeapFreeMagic: + return Magenta(); + case kAsanStackLeftRedzoneMagic: + case kAsanStackMidRedzoneMagic: + case kAsanStackRightRedzoneMagic: + case kAsanStackPartialRedzoneMagic: + return Red(); + case kAsanStackAfterReturnMagic: + return Magenta(); + case kAsanInitializationOrderMagic: + return Cyan(); + case kAsanUserPoisonedMemoryMagic: + return Blue(); + case kAsanStackUseAfterScopeMagic: + return Magenta(); + case kAsanGlobalRedzoneMagic: + return Red(); + case kAsanInternalHeapMagic: + return Yellow(); + default: + return Default(); + } + } + const char *EndShadowByte() { return Default(); } +}; + +// ---------------------- Helper functions ----------------------- {{{1 + +static void PrintShadowByte(const char *before, u8 byte, + const char *after = "\n") { + Decorator d; + Printf("%s%s%x%x%s%s", before, + d.ShadowByte(byte), byte >> 4, byte & 15, d.EndShadowByte(), after); +} + +static void PrintShadowBytes(const char *before, u8 *bytes, + u8 *guilty, uptr n) { + Decorator d; + if (before) + Printf("%s%p:", before, bytes); + for (uptr i = 0; i < n; i++) { + u8 *p = bytes + i; + const char *before = p == guilty ? "[" : + p - 1 == guilty ? "" : " "; + const char *after = p == guilty ? "]" : ""; + PrintShadowByte(before, *p, after); + } + Printf("\n"); +} + +static void PrintShadowMemoryForAddress(uptr addr) { + if (!AddrIsInMem(addr)) + return; + uptr shadow_addr = MemToShadow(addr); + const uptr n_bytes_per_row = 16; + uptr aligned_shadow = shadow_addr & ~(n_bytes_per_row - 1); + Printf("Shadow bytes around the buggy address:\n"); + for (int i = -5; i <= 5; i++) { + const char *prefix = (i == 0) ? "=>" : " "; + PrintShadowBytes(prefix, + (u8*)(aligned_shadow + i * n_bytes_per_row), + (u8*)shadow_addr, n_bytes_per_row); + } + Printf("Shadow byte legend (one shadow byte represents %d " + "application bytes):\n", (int)SHADOW_GRANULARITY); + PrintShadowByte(" Addressable: ", 0); + Printf(" Partially addressable: "); + for (uptr i = 1; i < SHADOW_GRANULARITY; i++) + PrintShadowByte("", i, " "); + Printf("\n"); + PrintShadowByte(" Heap left redzone: ", kAsanHeapLeftRedzoneMagic); + PrintShadowByte(" Heap righ redzone: ", kAsanHeapRightRedzoneMagic); + PrintShadowByte(" Freed Heap region: ", kAsanHeapFreeMagic); + PrintShadowByte(" Stack left redzone: ", kAsanStackLeftRedzoneMagic); + PrintShadowByte(" Stack mid redzone: ", kAsanStackMidRedzoneMagic); + PrintShadowByte(" Stack right redzone: ", kAsanStackRightRedzoneMagic); + PrintShadowByte(" Stack partial redzone: ", kAsanStackPartialRedzoneMagic); + PrintShadowByte(" Stack after return: ", kAsanStackAfterReturnMagic); + PrintShadowByte(" Stack use after scope: ", kAsanStackUseAfterScopeMagic); + PrintShadowByte(" Global redzone: ", kAsanGlobalRedzoneMagic); + PrintShadowByte(" Global init order: ", kAsanInitializationOrderMagic); + PrintShadowByte(" Poisoned by user: ", kAsanUserPoisonedMemoryMagic); + PrintShadowByte(" ASan internal: ", kAsanInternalHeapMagic); +} + +static void PrintZoneForPointer(uptr ptr, uptr zone_ptr, + const char *zone_name) { + if (zone_ptr) { + if (zone_name) { + Printf("malloc_zone_from_ptr(%p) = %p, which is %s\n", + ptr, zone_ptr, zone_name); + } else { + Printf("malloc_zone_from_ptr(%p) = %p, which doesn't have a name\n", + ptr, zone_ptr); + } + } else { + Printf("malloc_zone_from_ptr(%p) = 0\n", ptr); + } +} + +// ---------------------- Address Descriptions ------------------- {{{1 + +static bool IsASCII(unsigned char c) { + return /*0x00 <= c &&*/ c <= 0x7F; +} + +// Check if the global is a zero-terminated ASCII string. If so, print it. +static void PrintGlobalNameIfASCII(const __asan_global &g) { + for (uptr p = g.beg; p < g.beg + g.size - 1; p++) { + if (!IsASCII(*(unsigned char*)p)) return; + } + if (*(char*)(g.beg + g.size - 1) != 0) return; + Printf(" '%s' is ascii string '%s'\n", g.name, (char*)g.beg); +} + +bool DescribeAddressRelativeToGlobal(uptr addr, const __asan_global &g) { + if (addr < g.beg - kGlobalAndStackRedzone) return false; + if (addr >= g.beg + g.size_with_redzone) return false; + Decorator d; + Printf("%s", d.Location()); + Printf("%p is located ", (void*)addr); + if (addr < g.beg) { + Printf("%zd bytes to the left", g.beg - addr); + } else if (addr >= g.beg + g.size) { + Printf("%zd bytes to the right", addr - (g.beg + g.size)); + } else { + Printf("%zd bytes inside", addr - g.beg); // Can it happen? + } + Printf(" of global variable '%s' (0x%zx) of size %zu\n", + g.name, g.beg, g.size); + Printf("%s", d.EndLocation()); + PrintGlobalNameIfASCII(g); + return true; +} + +bool DescribeAddressIfShadow(uptr addr) { + if (AddrIsInMem(addr)) + return false; + static const char kAddrInShadowReport[] = + "Address %p is located in the %s.\n"; + if (AddrIsInShadowGap(addr)) { + Printf(kAddrInShadowReport, addr, "shadow gap area"); + return true; + } + if (AddrIsInHighShadow(addr)) { + Printf(kAddrInShadowReport, addr, "high shadow area"); + return true; + } + if (AddrIsInLowShadow(addr)) { + Printf(kAddrInShadowReport, addr, "low shadow area"); + return true; + } + CHECK(0 && "Address is not in memory and not in shadow?"); + return false; +} + +bool DescribeAddressIfStack(uptr addr, uptr access_size) { + AsanThread *t = asanThreadRegistry().FindThreadByStackAddress(addr); + if (!t) return false; + const sptr kBufSize = 4095; + char buf[kBufSize]; + uptr offset = 0; + const char *frame_descr = t->GetFrameNameByAddr(addr, &offset); + // This string is created by the compiler and has the following form: + // "FunctioName n alloc_1 alloc_2 ... alloc_n" + // where alloc_i looks like "offset size len ObjectName ". + CHECK(frame_descr); + // Report the function name and the offset. + const char *name_end = internal_strchr(frame_descr, ' '); + CHECK(name_end); + buf[0] = 0; + internal_strncat(buf, frame_descr, + Min(kBufSize, + static_cast<sptr>(name_end - frame_descr))); + Decorator d; + Printf("%s", d.Location()); + Printf("Address %p is located at offset %zu " + "in frame <%s> of T%d's stack:\n", + (void*)addr, offset, Demangle(buf), t->tid()); + Printf("%s", d.EndLocation()); + // Report the number of stack objects. + char *p; + uptr n_objects = internal_simple_strtoll(name_end, &p, 10); + CHECK(n_objects > 0); + Printf(" This frame has %zu object(s):\n", n_objects); + // Report all objects in this frame. + for (uptr i = 0; i < n_objects; i++) { + uptr beg, size; + sptr len; + beg = internal_simple_strtoll(p, &p, 10); + size = internal_simple_strtoll(p, &p, 10); + len = internal_simple_strtoll(p, &p, 10); + if (beg <= 0 || size <= 0 || len < 0 || *p != ' ') { + Printf("AddressSanitizer can't parse the stack frame " + "descriptor: |%s|\n", frame_descr); + break; + } + p++; + buf[0] = 0; + internal_strncat(buf, p, Min(kBufSize, len)); + p += len; + Printf(" [%zu, %zu) '%s'\n", beg, beg + size, buf); + } + Printf("HINT: this may be a false positive if your program uses " + "some custom stack unwind mechanism or swapcontext\n" + " (longjmp and C++ exceptions *are* supported)\n"); + DescribeThread(t->summary()); + return true; +} + +static void DescribeAccessToHeapChunk(AsanChunkView chunk, uptr addr, + uptr access_size) { + uptr offset; + Decorator d; + Printf("%s", d.Location()); + Printf("%p is located ", (void*)addr); + if (chunk.AddrIsInside(addr, access_size, &offset)) { + Printf("%zu bytes inside of", offset); + } else if (chunk.AddrIsAtLeft(addr, access_size, &offset)) { + Printf("%zu bytes to the left of", offset); + } else if (chunk.AddrIsAtRight(addr, access_size, &offset)) { + Printf("%zu bytes to the right of", offset); + } else { + Printf(" somewhere around (this is AddressSanitizer bug!)"); + } + Printf(" %zu-byte region [%p,%p)\n", chunk.UsedSize(), + (void*)(chunk.Beg()), (void*)(chunk.End())); + Printf("%s", d.EndLocation()); +} + +// Return " (thread_name) " or an empty string if the name is empty. +const char *ThreadNameWithParenthesis(AsanThreadSummary *t, char buff[], + uptr buff_len) { + const char *name = t->name(); + if (*name == 0) return ""; + buff[0] = 0; + internal_strncat(buff, " (", 3); + internal_strncat(buff, name, buff_len - 4); + internal_strncat(buff, ")", 2); + return buff; +} + +const char *ThreadNameWithParenthesis(u32 tid, char buff[], + uptr buff_len) { + if (tid == kInvalidTid) return ""; + AsanThreadSummary *t = asanThreadRegistry().FindByTid(tid); + return ThreadNameWithParenthesis(t, buff, buff_len); +} + +void DescribeHeapAddress(uptr addr, uptr access_size) { + AsanChunkView chunk = FindHeapChunkByAddress(addr); + if (!chunk.IsValid()) return; + DescribeAccessToHeapChunk(chunk, addr, access_size); + CHECK(chunk.AllocTid() != kInvalidTid); + AsanThreadSummary *alloc_thread = + asanThreadRegistry().FindByTid(chunk.AllocTid()); + StackTrace alloc_stack; + chunk.GetAllocStack(&alloc_stack); + AsanThread *t = asanThreadRegistry().GetCurrent(); + CHECK(t); + char tname[128]; + Decorator d; + if (chunk.FreeTid() != kInvalidTid) { + AsanThreadSummary *free_thread = + asanThreadRegistry().FindByTid(chunk.FreeTid()); + Printf("%sfreed by thread T%d%s here:%s\n", d.Allocation(), + free_thread->tid(), + ThreadNameWithParenthesis(free_thread, tname, sizeof(tname)), + d.EndAllocation()); + StackTrace free_stack; + chunk.GetFreeStack(&free_stack); + PrintStack(&free_stack); + Printf("%spreviously allocated by thread T%d%s here:%s\n", + d.Allocation(), alloc_thread->tid(), + ThreadNameWithParenthesis(alloc_thread, tname, sizeof(tname)), + d.EndAllocation()); + PrintStack(&alloc_stack); + DescribeThread(t->summary()); + DescribeThread(free_thread); + DescribeThread(alloc_thread); + } else { + Printf("%sallocated by thread T%d%s here:%s\n", d.Allocation(), + alloc_thread->tid(), + ThreadNameWithParenthesis(alloc_thread, tname, sizeof(tname)), + d.EndAllocation()); + PrintStack(&alloc_stack); + DescribeThread(t->summary()); + DescribeThread(alloc_thread); + } +} + +void DescribeAddress(uptr addr, uptr access_size) { + // Check if this is shadow or shadow gap. + if (DescribeAddressIfShadow(addr)) + return; + CHECK(AddrIsInMem(addr)); + if (DescribeAddressIfGlobal(addr)) + return; + if (DescribeAddressIfStack(addr, access_size)) + return; + // Assume it is a heap address. + DescribeHeapAddress(addr, access_size); +} + +// ------------------- Thread description -------------------- {{{1 + +void DescribeThread(AsanThreadSummary *summary) { + CHECK(summary); + // No need to announce the main thread. + if (summary->tid() == 0 || summary->announced()) { + return; + } + summary->set_announced(true); + char tname[128]; + Printf("Thread T%d%s", summary->tid(), + ThreadNameWithParenthesis(summary->tid(), tname, sizeof(tname))); + Printf(" created by T%d%s here:\n", + summary->parent_tid(), + ThreadNameWithParenthesis(summary->parent_tid(), + tname, sizeof(tname))); + PrintStack(summary->stack()); + // Recursively described parent thread if needed. + if (flags()->print_full_thread_history) { + AsanThreadSummary *parent_summary = + asanThreadRegistry().FindByTid(summary->parent_tid()); + DescribeThread(parent_summary); + } +} + +// -------------------- Different kinds of reports ----------------- {{{1 + +// Use ScopedInErrorReport to run common actions just before and +// immediately after printing error report. +class ScopedInErrorReport { + public: + ScopedInErrorReport() { + static atomic_uint32_t num_calls; + static u32 reporting_thread_tid; + if (atomic_fetch_add(&num_calls, 1, memory_order_relaxed) != 0) { + // Do not print more than one report, otherwise they will mix up. + // Error reporting functions shouldn't return at this situation, as + // they are defined as no-return. + Report("AddressSanitizer: while reporting a bug found another one." + "Ignoring.\n"); + u32 current_tid = asanThreadRegistry().GetCurrentTidOrInvalid(); + if (current_tid != reporting_thread_tid) { + // ASan found two bugs in different threads simultaneously. Sleep + // long enough to make sure that the thread which started to print + // an error report will finish doing it. + SleepForSeconds(Max(100, flags()->sleep_before_dying + 1)); + } + // If we're still not dead for some reason, use raw Exit() instead of + // Die() to bypass any additional checks. + Exit(flags()->exitcode); + } + ASAN_ON_ERROR(); + reporting_thread_tid = asanThreadRegistry().GetCurrentTidOrInvalid(); + Printf("====================================================" + "=============\n"); + if (reporting_thread_tid != kInvalidTid) { + // We started reporting an error message. Stop using the fake stack + // in case we call an instrumented function from a symbolizer. + AsanThread *curr_thread = asanThreadRegistry().GetCurrent(); + CHECK(curr_thread); + curr_thread->fake_stack().StopUsingFakeStack(); + } + } + // Destructor is NORETURN, as functions that report errors are. + NORETURN ~ScopedInErrorReport() { + // Make sure the current thread is announced. + AsanThread *curr_thread = asanThreadRegistry().GetCurrent(); + if (curr_thread) { + DescribeThread(curr_thread->summary()); + } + // Print memory stats. + __asan_print_accumulated_stats(); + if (error_report_callback) { + error_report_callback(error_message_buffer); + } + Report("ABORTING\n"); + Die(); + } +}; + +void ReportSIGSEGV(uptr pc, uptr sp, uptr bp, uptr addr) { + ScopedInErrorReport in_report; + Decorator d; + Printf("%s", d.Warning()); + Report("ERROR: AddressSanitizer: SEGV on unknown address %p" + " (pc %p sp %p bp %p T%d)\n", + (void*)addr, (void*)pc, (void*)sp, (void*)bp, + asanThreadRegistry().GetCurrentTidOrInvalid()); + Printf("%s", d.EndWarning()); + Printf("AddressSanitizer can not provide additional info.\n"); + GET_STACK_TRACE_FATAL(pc, bp); + PrintStack(&stack); +} + +void ReportDoubleFree(uptr addr, StackTrace *stack) { + ScopedInErrorReport in_report; + Decorator d; + Printf("%s", d.Warning()); + Report("ERROR: AddressSanitizer: attempting double-free on %p:\n", addr); + Printf("%s", d.EndWarning()); + PrintStack(stack); + DescribeHeapAddress(addr, 1); +} + +void ReportFreeNotMalloced(uptr addr, StackTrace *stack) { + ScopedInErrorReport in_report; + Decorator d; + Printf("%s", d.Warning()); + Report("ERROR: AddressSanitizer: attempting free on address " + "which was not malloc()-ed: %p\n", addr); + Printf("%s", d.EndWarning()); + PrintStack(stack); + DescribeHeapAddress(addr, 1); +} + +void ReportAllocTypeMismatch(uptr addr, StackTrace *stack, + AllocType alloc_type, + AllocType dealloc_type) { + static const char *alloc_names[] = + {"INVALID", "malloc", "operator new", "operator new []"}; + static const char *dealloc_names[] = + {"INVALID", "free", "operator delete", "operator delete []"}; + CHECK_NE(alloc_type, dealloc_type); + ScopedInErrorReport in_report; + Decorator d; + Printf("%s", d.Warning()); + Report("ERROR: AddressSanitizer: alloc-dealloc-mismatch (%s vs %s) on %p\n", + alloc_names[alloc_type], dealloc_names[dealloc_type], addr); + Printf("%s", d.EndWarning()); + PrintStack(stack); + DescribeHeapAddress(addr, 1); + Report("HINT: if you don't care about these warnings you may set " + "ASAN_OPTIONS=alloc_dealloc_mismatch=0\n"); +} + +void ReportMallocUsableSizeNotOwned(uptr addr, StackTrace *stack) { + ScopedInErrorReport in_report; + Decorator d; + Printf("%s", d.Warning()); + Report("ERROR: AddressSanitizer: attempting to call " + "malloc_usable_size() for pointer which is " + "not owned: %p\n", addr); + Printf("%s", d.EndWarning()); + PrintStack(stack); + DescribeHeapAddress(addr, 1); +} + +void ReportAsanGetAllocatedSizeNotOwned(uptr addr, StackTrace *stack) { + ScopedInErrorReport in_report; + Decorator d; + Printf("%s", d.Warning()); + Report("ERROR: AddressSanitizer: attempting to call " + "__asan_get_allocated_size() for pointer which is " + "not owned: %p\n", addr); + Printf("%s", d.EndWarning()); + PrintStack(stack); + DescribeHeapAddress(addr, 1); +} + +void ReportStringFunctionMemoryRangesOverlap( + const char *function, const char *offset1, uptr length1, + const char *offset2, uptr length2, StackTrace *stack) { + ScopedInErrorReport in_report; + Decorator d; + Printf("%s", d.Warning()); + Report("ERROR: AddressSanitizer: %s-param-overlap: " + "memory ranges [%p,%p) and [%p, %p) overlap\n", \ + function, offset1, offset1 + length1, offset2, offset2 + length2); + Printf("%s", d.EndWarning()); + PrintStack(stack); + DescribeAddress((uptr)offset1, length1); + DescribeAddress((uptr)offset2, length2); +} + +// ----------------------- Mac-specific reports ----------------- {{{1 + +void WarnMacFreeUnallocated( + uptr addr, uptr zone_ptr, const char *zone_name, StackTrace *stack) { + // Just print a warning here. + Printf("free_common(%p) -- attempting to free unallocated memory.\n" + "AddressSanitizer is ignoring this error on Mac OS now.\n", + addr); + PrintZoneForPointer(addr, zone_ptr, zone_name); + PrintStack(stack); + DescribeHeapAddress(addr, 1); +} + +void ReportMacMzReallocUnknown( + uptr addr, uptr zone_ptr, const char *zone_name, StackTrace *stack) { + ScopedInErrorReport in_report; + Printf("mz_realloc(%p) -- attempting to realloc unallocated memory.\n" + "This is an unrecoverable problem, exiting now.\n", + addr); + PrintZoneForPointer(addr, zone_ptr, zone_name); + PrintStack(stack); + DescribeHeapAddress(addr, 1); +} + +void ReportMacCfReallocUnknown( + uptr addr, uptr zone_ptr, const char *zone_name, StackTrace *stack) { + ScopedInErrorReport in_report; + Printf("cf_realloc(%p) -- attempting to realloc unallocated memory.\n" + "This is an unrecoverable problem, exiting now.\n", + addr); + PrintZoneForPointer(addr, zone_ptr, zone_name); + PrintStack(stack); + DescribeHeapAddress(addr, 1); +} + +} // namespace __asan + +// --------------------------- Interface --------------------- {{{1 +using namespace __asan; // NOLINT + +void __asan_report_error(uptr pc, uptr bp, uptr sp, + uptr addr, bool is_write, uptr access_size) { + ScopedInErrorReport in_report; + + // Determine the error type. + const char *bug_descr = "unknown-crash"; + if (AddrIsInMem(addr)) { + u8 *shadow_addr = (u8*)MemToShadow(addr); + // If we are accessing 16 bytes, look at the second shadow byte. + if (*shadow_addr == 0 && access_size > SHADOW_GRANULARITY) + shadow_addr++; + // If we are in the partial right redzone, look at the next shadow byte. + if (*shadow_addr > 0 && *shadow_addr < 128) + shadow_addr++; + switch (*shadow_addr) { + case kAsanHeapLeftRedzoneMagic: + case kAsanHeapRightRedzoneMagic: + bug_descr = "heap-buffer-overflow"; + break; + case kAsanHeapFreeMagic: + bug_descr = "heap-use-after-free"; + break; + case kAsanStackLeftRedzoneMagic: + bug_descr = "stack-buffer-underflow"; + break; + case kAsanInitializationOrderMagic: + bug_descr = "initialization-order-fiasco"; + break; + case kAsanStackMidRedzoneMagic: + case kAsanStackRightRedzoneMagic: + case kAsanStackPartialRedzoneMagic: + bug_descr = "stack-buffer-overflow"; + break; + case kAsanStackAfterReturnMagic: + bug_descr = "stack-use-after-return"; + break; + case kAsanUserPoisonedMemoryMagic: + bug_descr = "use-after-poison"; + break; + case kAsanStackUseAfterScopeMagic: + bug_descr = "stack-use-after-scope"; + break; + case kAsanGlobalRedzoneMagic: + bug_descr = "global-buffer-overflow"; + break; + } + } + Decorator d; + Printf("%s", d.Warning()); + Report("ERROR: AddressSanitizer: %s on address " + "%p at pc 0x%zx bp 0x%zx sp 0x%zx\n", + bug_descr, (void*)addr, pc, bp, sp); + Printf("%s", d.EndWarning()); + + u32 curr_tid = asanThreadRegistry().GetCurrentTidOrInvalid(); + char tname[128]; + Printf("%s%s of size %zu at %p thread T%d%s%s\n", + d.Access(), + access_size ? (is_write ? "WRITE" : "READ") : "ACCESS", + access_size, (void*)addr, curr_tid, + ThreadNameWithParenthesis(curr_tid, tname, sizeof(tname)), + d.EndAccess()); + + GET_STACK_TRACE_FATAL(pc, bp); + PrintStack(&stack); + + DescribeAddress(addr, access_size); + + PrintShadowMemoryForAddress(addr); +} + +void NOINLINE __asan_set_error_report_callback(void (*callback)(const char*)) { + error_report_callback = callback; + if (callback) { + error_message_buffer_size = 1 << 16; + error_message_buffer = + (char*)MmapOrDie(error_message_buffer_size, __FUNCTION__); + error_message_buffer_pos = 0; + } +} + +void __asan_describe_address(uptr addr) { + DescribeAddress(addr, 1); +} + +#if !SANITIZER_SUPPORTS_WEAK_HOOKS +// Provide default implementation of __asan_on_error that does nothing +// and may be overriden by user. +SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE NOINLINE +void __asan_on_error() {} +#endif diff --git a/lib/asan/asan_report.h b/lib/asan/asan_report.h new file mode 100644 index 000000000000..f0617f91970e --- /dev/null +++ b/lib/asan/asan_report.h @@ -0,0 +1,57 @@ +//===-- asan_report.h -------------------------------------------*- C++ -*-===// +// +// 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. +// +// ASan-private header for error reporting functions. +//===----------------------------------------------------------------------===// + +#include "asan_allocator.h" +#include "asan_internal.h" +#include "asan_thread.h" +#include "sanitizer/asan_interface.h" + +namespace __asan { + +// The following functions prints address description depending +// on the memory type (shadow/heap/stack/global). +void DescribeHeapAddress(uptr addr, uptr access_size); +bool DescribeAddressIfGlobal(uptr addr); +bool DescribeAddressRelativeToGlobal(uptr addr, const __asan_global &g); +bool DescribeAddressIfShadow(uptr addr); +bool DescribeAddressIfStack(uptr addr, uptr access_size); +// Determines memory type on its own. +void DescribeAddress(uptr addr, uptr access_size); + +void DescribeThread(AsanThreadSummary *summary); + +// Different kinds of error reports. +void NORETURN ReportSIGSEGV(uptr pc, uptr sp, uptr bp, uptr addr); +void NORETURN ReportDoubleFree(uptr addr, StackTrace *stack); +void NORETURN ReportFreeNotMalloced(uptr addr, StackTrace *stack); +void NORETURN ReportAllocTypeMismatch(uptr addr, StackTrace *stack, + AllocType alloc_type, + AllocType dealloc_type); +void NORETURN ReportMallocUsableSizeNotOwned(uptr addr, + StackTrace *stack); +void NORETURN ReportAsanGetAllocatedSizeNotOwned(uptr addr, + StackTrace *stack); +void NORETURN ReportStringFunctionMemoryRangesOverlap( + const char *function, const char *offset1, uptr length1, + const char *offset2, uptr length2, StackTrace *stack); + +// Mac-specific errors and warnings. +void WarnMacFreeUnallocated( + uptr addr, uptr zone_ptr, const char *zone_name, StackTrace *stack); +void NORETURN ReportMacMzReallocUnknown( + uptr addr, uptr zone_ptr, const char *zone_name, StackTrace *stack); +void NORETURN ReportMacCfReallocUnknown( + uptr addr, uptr zone_ptr, const char *zone_name, StackTrace *stack); + +} // namespace __asan diff --git a/lib/asan/asan_rtl.cc b/lib/asan/asan_rtl.cc index 34324fa16d08..11adbee5bdea 100644 --- a/lib/asan/asan_rtl.cc +++ b/lib/asan/asan_rtl.cc @@ -13,29 +13,29 @@ //===----------------------------------------------------------------------===// #include "asan_allocator.h" #include "asan_interceptors.h" -#include "asan_interface.h" #include "asan_internal.h" -#include "asan_lock.h" #include "asan_mapping.h" +#include "asan_report.h" #include "asan_stack.h" #include "asan_stats.h" #include "asan_thread.h" #include "asan_thread_registry.h" +#include "sanitizer/asan_interface.h" #include "sanitizer_common/sanitizer_atomic.h" #include "sanitizer_common/sanitizer_flags.h" #include "sanitizer_common/sanitizer_libc.h" +#include "sanitizer_common/sanitizer_symbolizer.h" -namespace __sanitizer { -using namespace __asan; +namespace __asan { -void Die() { +static void AsanDie() { static atomic_uint32_t num_calls; if (atomic_fetch_add(&num_calls, 1, memory_order_relaxed) != 0) { // Don't die twice - run a busy loop. while (1) { } } if (flags()->sleep_before_dying) { - Report("Sleeping for %zd second(s)\n", flags()->sleep_before_dying); + Report("Sleeping for %d second(s)\n", flags()->sleep_before_dying); SleepForSeconds(flags()->sleep_before_dying); } if (flags()->unmap_shadow_on_exit) @@ -47,19 +47,17 @@ void Die() { Exit(flags()->exitcode); } -void CheckFailed(const char *file, int line, const char *cond, u64 v1, u64 v2) { - AsanReport("AddressSanitizer CHECK failed: %s:%d \"%s\" (0x%zx, 0x%zx)\n", +static void AsanCheckFailed(const char *file, int line, const char *cond, + u64 v1, u64 v2) { + Report("AddressSanitizer CHECK failed: %s:%d \"%s\" (0x%zx, 0x%zx)\n", file, line, cond, (uptr)v1, (uptr)v2); + // FIXME: check for infinite recursion without a thread-local counter here. PRINT_CURRENT_STACK(); - ShowStatsAndAbort(); + Die(); } -} // namespace __sanitizer - -namespace __asan { - // -------------------------- Flags ------------------------- {{{1 -static const int kMallocContextSize = 30; +static const int kDeafultMallocContextSize = 30; static Flags asan_flags; @@ -67,6 +65,10 @@ Flags *flags() { return &asan_flags; } +static const char *MaybeCallAsanDefaultOptions() { + return (&__asan_default_options) ? __asan_default_options() : ""; +} + static void ParseFlagsFromString(Flags *f, const char *str) { ParseFlag(str, &f->quarantine_size, "quarantine_size"); ParseFlag(str, &f->symbolize, "symbolize"); @@ -77,8 +79,9 @@ static void ParseFlagsFromString(Flags *f, const char *str) { ParseFlag(str, &f->debug, "debug"); ParseFlag(str, &f->report_globals, "report_globals"); + ParseFlag(str, &f->check_initialization_order, "initialization_order"); ParseFlag(str, &f->malloc_context_size, "malloc_context_size"); - CHECK(f->malloc_context_size <= kMallocContextSize); + CHECK((uptr)f->malloc_context_size <= kStackTraceMax); ParseFlag(str, &f->replace_str, "replace_str"); ParseFlag(str, &f->replace_intrin, "replace_intrin"); @@ -96,22 +99,28 @@ static void ParseFlagsFromString(Flags *f, const char *str) { ParseFlag(str, &f->abort_on_error, "abort_on_error"); ParseFlag(str, &f->atexit, "atexit"); ParseFlag(str, &f->disable_core, "disable_core"); + ParseFlag(str, &f->strip_path_prefix, "strip_path_prefix"); + ParseFlag(str, &f->allow_reexec, "allow_reexec"); + ParseFlag(str, &f->print_full_thread_history, "print_full_thread_history"); + ParseFlag(str, &f->log_path, "log_path"); + ParseFlag(str, &f->fast_unwind_on_fatal, "fast_unwind_on_fatal"); + ParseFlag(str, &f->fast_unwind_on_malloc, "fast_unwind_on_malloc"); + ParseFlag(str, &f->poison_heap, "poison_heap"); + ParseFlag(str, &f->alloc_dealloc_mismatch, "alloc_dealloc_mismatch"); + ParseFlag(str, &f->use_stack_depot, "use_stack_depot"); } -extern "C" { -const char* WEAK __asan_default_options() { return ""; } -} // extern "C" - void InitializeFlags(Flags *f, const char *env) { internal_memset(f, 0, sizeof(*f)); - f->quarantine_size = (ASAN_LOW_MEMORY) ? 1UL << 24 : 1UL << 28; + f->quarantine_size = (ASAN_LOW_MEMORY) ? 1UL << 26 : 1UL << 28; f->symbolize = false; f->verbosity = 0; - f->redzone = (ASAN_LOW_MEMORY) ? 64 : 128; + f->redzone = ASAN_ALLOCATOR_VERSION == 2 ? 16 : (ASAN_LOW_MEMORY) ? 64 : 128; f->debug = false; f->report_globals = 1; - f->malloc_context_size = kMallocContextSize; + f->check_initialization_order = true; + f->malloc_context_size = kDeafultMallocContextSize; f->replace_str = true; f->replace_intrin = true; f->replace_cfallocator = true; @@ -127,13 +136,24 @@ void InitializeFlags(Flags *f, const char *env) { f->unmap_shadow_on_exit = false; f->abort_on_error = false; f->atexit = false; - f->disable_core = (__WORDSIZE == 64); + f->disable_core = (SANITIZER_WORDSIZE == 64); + f->strip_path_prefix = ""; + f->allow_reexec = true; + f->print_full_thread_history = true; + f->log_path = 0; + f->fast_unwind_on_fatal = false; + f->fast_unwind_on_malloc = true; + f->poison_heap = true; + // Turn off alloc/dealloc mismatch checker on Mac for now. + // TODO(glider): Fix known issues and enable this back. + f->alloc_dealloc_mismatch = (ASAN_MAC == 0); + f->use_stack_depot = true; // Only affects allocator2. // Override from user-specified string. - ParseFlagsFromString(f, __asan_default_options()); + ParseFlagsFromString(f, MaybeCallAsanDefaultOptions()); if (flags()->verbosity) { Report("Using the defaults from __asan_default_options: %s\n", - __asan_default_options()); + MaybeCallAsanDefaultOptions()); } // Override from command line. @@ -144,10 +164,6 @@ void InitializeFlags(Flags *f, const char *env) { int asan_inited; bool asan_init_is_running; void (*death_callback)(void); -static void (*error_report_callback)(const char*); -char *error_message_buffer = 0; -uptr error_message_buffer_pos = 0; -uptr error_message_buffer_size = 0; // -------------------------- Misc ---------------- {{{1 void ShowStatsAndAbort() { @@ -155,146 +171,23 @@ void ShowStatsAndAbort() { Die(); } -static void PrintBytes(const char *before, uptr *a) { - u8 *bytes = (u8*)a; - uptr byte_num = (__WORDSIZE) / 8; - AsanPrintf("%s%p:", before, (void*)a); - for (uptr i = 0; i < byte_num; i++) { - AsanPrintf(" %x%x", bytes[i] >> 4, bytes[i] & 15); - } - AsanPrintf("\n"); -} - -void AppendToErrorMessageBuffer(const char *buffer) { - if (error_message_buffer) { - uptr length = internal_strlen(buffer); - CHECK_GE(error_message_buffer_size, error_message_buffer_pos); - uptr remaining = error_message_buffer_size - error_message_buffer_pos; - internal_strncpy(error_message_buffer + error_message_buffer_pos, - buffer, remaining); - error_message_buffer[error_message_buffer_size - 1] = '\0'; - // FIXME: reallocate the buffer instead of truncating the message. - error_message_buffer_pos += remaining > length ? length : remaining; - } -} - // ---------------------- mmap -------------------- {{{1 // Reserve memory range [beg, end]. static void ReserveShadowMemoryRange(uptr beg, uptr end) { - CHECK((beg % kPageSize) == 0); - CHECK(((end + 1) % kPageSize) == 0); + CHECK((beg % GetPageSizeCached()) == 0); + CHECK(((end + 1) % GetPageSizeCached()) == 0); uptr size = end - beg + 1; void *res = MmapFixedNoReserve(beg, size); - CHECK(res == (void*)beg && "ReserveShadowMemoryRange failed"); -} - -// ---------------------- LowLevelAllocator ------------- {{{1 -void *LowLevelAllocator::Allocate(uptr size) { - CHECK((size & (size - 1)) == 0 && "size must be a power of two"); - if (allocated_end_ - allocated_current_ < (sptr)size) { - uptr size_to_allocate = Max(size, kPageSize); - allocated_current_ = - (char*)MmapOrDie(size_to_allocate, __FUNCTION__); - allocated_end_ = allocated_current_ + size_to_allocate; - PoisonShadow((uptr)allocated_current_, size_to_allocate, - kAsanInternalHeapMagic); - } - CHECK(allocated_end_ - allocated_current_ >= (sptr)size); - void *res = allocated_current_; - allocated_current_ += size; - return res; -} - -// ---------------------- DescribeAddress -------------------- {{{1 -static bool DescribeStackAddress(uptr addr, uptr access_size) { - AsanThread *t = asanThreadRegistry().FindThreadByStackAddress(addr); - if (!t) return false; - const sptr kBufSize = 4095; - char buf[kBufSize]; - uptr offset = 0; - const char *frame_descr = t->GetFrameNameByAddr(addr, &offset); - // This string is created by the compiler and has the following form: - // "FunctioName n alloc_1 alloc_2 ... alloc_n" - // where alloc_i looks like "offset size len ObjectName ". - CHECK(frame_descr); - // Report the function name and the offset. - const char *name_end = internal_strchr(frame_descr, ' '); - CHECK(name_end); - buf[0] = 0; - internal_strncat(buf, frame_descr, - Min(kBufSize, - static_cast<sptr>(name_end - frame_descr))); - AsanPrintf("Address %p is located at offset %zu " - "in frame <%s> of T%d's stack:\n", - (void*)addr, offset, buf, t->tid()); - // Report the number of stack objects. - char *p; - uptr n_objects = internal_simple_strtoll(name_end, &p, 10); - CHECK(n_objects > 0); - AsanPrintf(" This frame has %zu object(s):\n", n_objects); - // Report all objects in this frame. - for (uptr i = 0; i < n_objects; i++) { - uptr beg, size; - sptr len; - beg = internal_simple_strtoll(p, &p, 10); - size = internal_simple_strtoll(p, &p, 10); - len = internal_simple_strtoll(p, &p, 10); - if (beg <= 0 || size <= 0 || len < 0 || *p != ' ') { - AsanPrintf("AddressSanitizer can't parse the stack frame " - "descriptor: |%s|\n", frame_descr); - break; - } - p++; - buf[0] = 0; - internal_strncat(buf, p, Min(kBufSize, len)); - p += len; - AsanPrintf(" [%zu, %zu) '%s'\n", beg, beg + size, buf); - } - AsanPrintf("HINT: this may be a false positive if your program uses " - "some custom stack unwind mechanism\n" - " (longjmp and C++ exceptions *are* supported)\n"); - t->summary()->Announce(); - return true; -} - -static bool DescribeAddrIfShadow(uptr addr) { - if (AddrIsInMem(addr)) - return false; - static const char kAddrInShadowReport[] = - "Address %p is located in the %s.\n"; - if (AddrIsInShadowGap(addr)) { - AsanPrintf(kAddrInShadowReport, addr, "shadow gap area"); - return true; - } - if (AddrIsInHighShadow(addr)) { - AsanPrintf(kAddrInShadowReport, addr, "high shadow area"); - return true; - } - if (AddrIsInLowShadow(addr)) { - AsanPrintf(kAddrInShadowReport, addr, "low shadow area"); - return true; + if (res != (void*)beg) { + Report("ReserveShadowMemoryRange failed while trying to map 0x%zx bytes. " + "Perhaps you're using ulimit -v\n", size); + Abort(); } - - CHECK(0); // Unreachable. - return false; } -static NOINLINE void DescribeAddress(uptr addr, uptr access_size) { - // Check if this is shadow or shadow gap. - if (DescribeAddrIfShadow(addr)) - return; - - CHECK(AddrIsInMem(addr)); - - // Check if this is a global. - if (DescribeAddrIfGlobal(addr)) - return; - - if (DescribeStackAddress(addr, access_size)) - return; - - // finally, check if this is a heap. - DescribeHeapAddress(addr, access_size); +// --------------- LowLevelAllocateCallbac ---------- {{{1 +static void OnLowLevelAllocate(uptr ptr, uptr size) { + PoisonShadow(ptr, size, kAsanInternalHeapMagic); } // -------------------------- Run-time entry ------------------- {{{1 @@ -325,29 +218,48 @@ ASAN_REPORT_ERROR(store, true, 16) // time. static NOINLINE void force_interface_symbols() { volatile int fake_condition = 0; // prevent dead condition elimination. - if (fake_condition) { - __asan_report_load1(0); - __asan_report_load2(0); - __asan_report_load4(0); - __asan_report_load8(0); - __asan_report_load16(0); - __asan_report_store1(0); - __asan_report_store2(0); - __asan_report_store4(0); - __asan_report_store8(0); - __asan_report_store16(0); - __asan_register_global(0, 0, 0); - __asan_register_globals(0, 0); - __asan_unregister_globals(0, 0); - __asan_set_death_callback(0); - __asan_set_error_report_callback(0); - __asan_handle_no_return(); + // __asan_report_* functions are noreturn, so we need a switch to prevent + // the compiler from removing any of them. + switch (fake_condition) { + case 1: __asan_report_load1(0); break; + case 2: __asan_report_load2(0); break; + case 3: __asan_report_load4(0); break; + case 4: __asan_report_load8(0); break; + case 5: __asan_report_load16(0); break; + case 6: __asan_report_store1(0); break; + case 7: __asan_report_store2(0); break; + case 8: __asan_report_store4(0); break; + case 9: __asan_report_store8(0); break; + case 10: __asan_report_store16(0); break; + case 12: __asan_register_globals(0, 0); break; + case 13: __asan_unregister_globals(0, 0); break; + case 14: __asan_set_death_callback(0); break; + case 15: __asan_set_error_report_callback(0); break; + case 16: __asan_handle_no_return(); break; + case 17: __asan_address_is_poisoned(0); break; + case 18: __asan_get_allocated_size(0); break; + case 19: __asan_get_current_allocated_bytes(); break; + case 20: __asan_get_estimated_allocated_size(0); break; + case 21: __asan_get_free_bytes(); break; + case 22: __asan_get_heap_size(); break; + case 23: __asan_get_ownership(0); break; + case 24: __asan_get_unmapped_bytes(); break; + case 25: __asan_poison_memory_region(0, 0); break; + case 26: __asan_unpoison_memory_region(0, 0); break; + case 27: __asan_set_error_exit_code(0); break; + case 28: __asan_stack_free(0, 0, 0); break; + case 29: __asan_stack_malloc(0, 0); break; + case 30: __asan_before_dynamic_init(0, 0); break; + case 31: __asan_after_dynamic_init(); break; + case 32: __asan_poison_stack_memory(0, 0); break; + case 33: __asan_unpoison_stack_memory(0, 0); break; + case 34: __asan_region_is_poisoned(0, 0); break; + case 35: __asan_describe_address(0); break; } } -// -------------------------- Init ------------------- {{{1 static void asan_atexit() { - AsanPrintf("AddressSanitizer exit stats:\n"); + Printf("AddressSanitizer exit stats:\n"); __asan_print_accumulated_stats(); } @@ -356,7 +268,14 @@ static void asan_atexit() { // ---------------------- Interface ---------------- {{{1 using namespace __asan; // NOLINT -int __asan_set_error_exit_code(int exit_code) { +#if !SANITIZER_SUPPORTS_WEAK_HOOKS +extern "C" { +SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE +const char* __asan_default_options() { return ""; } +} // extern "C" +#endif + +int NOINLINE __asan_set_error_exit_code(int exit_code) { int old = flags()->exitcode; flags()->exitcode = exit_code; return old; @@ -366,8 +285,9 @@ void NOINLINE __asan_handle_no_return() { int local_stack; AsanThread *curr_thread = asanThreadRegistry().GetCurrent(); CHECK(curr_thread); + uptr PageSize = GetPageSizeCached(); uptr top = curr_thread->stack_top(); - uptr bottom = ((uptr)&local_stack - kPageSize) & ~(kPageSize-1); + uptr bottom = ((uptr)&local_stack - PageSize) & ~(PageSize-1); PoisonShadow(bottom, top - bottom, 0); } @@ -375,134 +295,35 @@ void NOINLINE __asan_set_death_callback(void (*callback)(void)) { death_callback = callback; } -void NOINLINE __asan_set_error_report_callback(void (*callback)(const char*)) { - error_report_callback = callback; - if (callback) { - error_message_buffer_size = 1 << 16; - error_message_buffer = - (char*)MmapOrDie(error_message_buffer_size, __FUNCTION__); - error_message_buffer_pos = 0; - } -} - -void __asan_report_error(uptr pc, uptr bp, uptr sp, - uptr addr, bool is_write, uptr access_size) { - static atomic_uint32_t num_calls; - if (atomic_fetch_add(&num_calls, 1, memory_order_relaxed) != 0) { - // Do not print more than one report, otherwise they will mix up. - // We can not return here because the function is marked as never-return. - AsanPrintf("AddressSanitizer: while reporting a bug found another one." - "Ignoring.\n"); - SleepForSeconds(5); - Die(); - } - - AsanPrintf("====================================================" - "=============\n"); - const char *bug_descr = "unknown-crash"; - if (AddrIsInMem(addr)) { - u8 *shadow_addr = (u8*)MemToShadow(addr); - // If we are accessing 16 bytes, look at the second shadow byte. - if (*shadow_addr == 0 && access_size > SHADOW_GRANULARITY) - shadow_addr++; - // If we are in the partial right redzone, look at the next shadow byte. - if (*shadow_addr > 0 && *shadow_addr < 128) - shadow_addr++; - switch (*shadow_addr) { - case kAsanHeapLeftRedzoneMagic: - case kAsanHeapRightRedzoneMagic: - bug_descr = "heap-buffer-overflow"; - break; - case kAsanHeapFreeMagic: - bug_descr = "heap-use-after-free"; - break; - case kAsanStackLeftRedzoneMagic: - bug_descr = "stack-buffer-underflow"; - break; - case kAsanStackMidRedzoneMagic: - case kAsanStackRightRedzoneMagic: - case kAsanStackPartialRedzoneMagic: - bug_descr = "stack-buffer-overflow"; - break; - case kAsanStackAfterReturnMagic: - bug_descr = "stack-use-after-return"; - break; - case kAsanUserPoisonedMemoryMagic: - bug_descr = "use-after-poison"; - break; - case kAsanGlobalRedzoneMagic: - bug_descr = "global-buffer-overflow"; - break; - } - } - - AsanThread *curr_thread = asanThreadRegistry().GetCurrent(); - u32 curr_tid = asanThreadRegistry().GetCurrentTidOrInvalid(); - - if (curr_thread) { - // We started reporting an error message. Stop using the fake stack - // in case we will call an instrumented function from a symbolizer. - curr_thread->fake_stack().StopUsingFakeStack(); - } - - AsanReport("ERROR: AddressSanitizer %s on address " - "%p at pc 0x%zx bp 0x%zx sp 0x%zx\n", - bug_descr, (void*)addr, pc, bp, sp); - - AsanPrintf("%s of size %zu at %p thread T%d\n", - access_size ? (is_write ? "WRITE" : "READ") : "ACCESS", - access_size, (void*)addr, curr_tid); - - if (flags()->debug) { - PrintBytes("PC: ", (uptr*)pc); - } - - GET_STACK_TRACE_WITH_PC_AND_BP(kStackTraceMax, pc, bp); - stack.PrintStack(); - - DescribeAddress(addr, access_size); - - if (AddrIsInMem(addr)) { - uptr shadow_addr = MemToShadow(addr); - AsanReport("ABORTING\n"); - __asan_print_accumulated_stats(); - AsanPrintf("Shadow byte and word:\n"); - AsanPrintf(" %p: %x\n", (void*)shadow_addr, *(unsigned char*)shadow_addr); - uptr aligned_shadow = shadow_addr & ~(kWordSize - 1); - PrintBytes(" ", (uptr*)(aligned_shadow)); - AsanPrintf("More shadow bytes:\n"); - PrintBytes(" ", (uptr*)(aligned_shadow-4*kWordSize)); - PrintBytes(" ", (uptr*)(aligned_shadow-3*kWordSize)); - PrintBytes(" ", (uptr*)(aligned_shadow-2*kWordSize)); - PrintBytes(" ", (uptr*)(aligned_shadow-1*kWordSize)); - PrintBytes("=>", (uptr*)(aligned_shadow+0*kWordSize)); - PrintBytes(" ", (uptr*)(aligned_shadow+1*kWordSize)); - PrintBytes(" ", (uptr*)(aligned_shadow+2*kWordSize)); - PrintBytes(" ", (uptr*)(aligned_shadow+3*kWordSize)); - PrintBytes(" ", (uptr*)(aligned_shadow+4*kWordSize)); - } - if (error_report_callback) { - error_report_callback(error_message_buffer); - } - Die(); -} - - void __asan_init() { if (asan_inited) return; + CHECK(!asan_init_is_running && "ASan init calls itself!"); asan_init_is_running = true; // Make sure we are not statically linked. AsanDoesNotSupportStaticLinkage(); - // Initialize flags. + // Install tool-specific callbacks in sanitizer_common. + SetDieCallback(AsanDie); + SetCheckFailedCallback(AsanCheckFailed); + SetPrintfAndReportCallback(AppendToErrorMessageBuffer); + + // Initialize flags. This must be done early, because most of the + // initialization steps look at flags(). const char *options = GetEnv("ASAN_OPTIONS"); InitializeFlags(flags(), options); + __sanitizer_set_report_path(flags()->log_path); if (flags()->verbosity && options) { Report("Parsed ASAN_OPTIONS: %s\n", options); } + // Re-exec ourselves if we need to set additional env or command line args. + MaybeReexec(); + + // Setup internal allocator callback. + SetLowLevelAllocateCallback(OnLowLevelAllocate); + if (flags()->atexit) { Atexit(asan_atexit); } @@ -543,12 +364,13 @@ void __asan_init() { } uptr shadow_start = kLowShadowBeg; - if (kLowShadowBeg > 0) shadow_start -= kMmapGranularity; + if (kLowShadowBeg > 0) shadow_start -= GetMmapGranularity(); uptr shadow_end = kHighShadowEnd; if (MemoryRangeIsAvailable(shadow_start, shadow_end)) { if (kLowShadowBeg != kLowShadowEnd) { // mmap the low shadow plus at least one page. - ReserveShadowMemoryRange(kLowShadowBeg - kMmapGranularity, kLowShadowEnd); + ReserveShadowMemoryRange(kLowShadowBeg - GetMmapGranularity(), + kLowShadowEnd); } // mmap the high shadow. ReserveShadowMemoryRange(kHighShadowBeg, kHighShadowEnd); @@ -563,6 +385,13 @@ void __asan_init() { } InstallSignalHandlers(); + // Start symbolizer process if necessary. + if (flags()->symbolize) { + const char *external_symbolizer = GetEnv("ASAN_SYMBOLIZER_PATH"); + if (external_symbolizer) { + InitializeExternalSymbolizer(external_symbolizer); + } + } // On Linux AsanThread::ThreadStart() calls malloc() that's why asan_inited // should be set to 1 prior to initializing the threads. diff --git a/lib/asan/asan_stack.cc b/lib/asan/asan_stack.cc index d6103c2c98fa..ebf22fd34ca1 100644 --- a/lib/asan/asan_stack.cc +++ b/lib/asan/asan_stack.cc @@ -11,222 +11,33 @@ // // Code for ASan stack trace. //===----------------------------------------------------------------------===// -#include "asan_interceptors.h" -#include "asan_lock.h" +#include "asan_flags.h" #include "asan_stack.h" -#include "asan_thread.h" -#include "asan_thread_registry.h" -#include "sanitizer_common/sanitizer_procmaps.h" -#include "sanitizer_common/sanitizer_symbolizer.h" - -#ifdef ASAN_USE_EXTERNAL_SYMBOLIZER -extern bool -ASAN_USE_EXTERNAL_SYMBOLIZER(const void *pc, char *out, int out_size); -#endif +#include "sanitizer/asan_interface.h" namespace __asan { -// ----------------------- AsanStackTrace ----------------------------- {{{1 -// PCs in stack traces are actually the return addresses, that is, -// addresses of the next instructions after the call. That's why we -// decrement them. -static uptr patch_pc(uptr pc) { -#ifdef __arm__ - // Cancel Thumb bit. - pc = pc & (~1); -#endif - return pc - 1; +static bool MaybeCallAsanSymbolize(const void *pc, char *out_buffer, + int out_size) { + return (&__asan_symbolize) ? __asan_symbolize(pc, out_buffer, out_size) + : false; } -#if defined(ASAN_USE_EXTERNAL_SYMBOLIZER) -void AsanStackTrace::PrintStack(uptr *addr, uptr size) { - for (uptr i = 0; i < size && addr[i]; i++) { - uptr pc = addr[i]; - if (i < size - 1 && addr[i + 1]) - pc = patch_pc(pc); - char buff[4096]; - ASAN_USE_EXTERNAL_SYMBOLIZER((void*)pc, buff, sizeof(buff)); - AsanPrintf(" #%zu 0x%zx %s\n", i, pc, buff); - } +void PrintStack(StackTrace *stack) { + stack->PrintStack(stack->trace, stack->size, flags()->symbolize, + flags()->strip_path_prefix, MaybeCallAsanSymbolize); } -#else // ASAN_USE_EXTERNAL_SYMBOLIZER -void AsanStackTrace::PrintStack(uptr *addr, uptr size) { - ProcessMaps proc_maps; - uptr frame_num = 0; - for (uptr i = 0; i < size && addr[i]; i++) { - uptr pc = addr[i]; - if (i < size - 1 && addr[i + 1]) - pc = patch_pc(pc); - AddressInfo addr_frames[64]; - uptr addr_frames_num = 0; - if (flags()->symbolize) { - addr_frames_num = SymbolizeCode(pc, addr_frames, - ASAN_ARRAY_SIZE(addr_frames)); - } - if (addr_frames_num > 0) { - for (uptr j = 0; j < addr_frames_num; j++) { - AddressInfo &info = addr_frames[j]; - AsanPrintf(" #%zu 0x%zx", frame_num, pc); - if (info.function) { - AsanPrintf(" in %s", info.function); - } - if (info.file) { - AsanPrintf(" %s:%d:%d", info.file, info.line, info.column); - } else if (info.module) { - AsanPrintf(" (%s+0x%zx)", info.module, info.module_offset); - } - AsanPrintf("\n"); - info.Clear(); - frame_num++; - } - } else { - uptr offset; - char filename[4096]; - if (proc_maps.GetObjectNameAndOffset(pc, &offset, - filename, sizeof(filename))) { - AsanPrintf(" #%zu 0x%zx (%s+0x%zx)\n", frame_num, pc, filename, - offset); - } else { - AsanPrintf(" #%zu 0x%zx\n", frame_num, pc); - } - frame_num++; - } - } -} -#endif // ASAN_USE_EXTERNAL_SYMBOLIZER +} // namespace __asan -uptr AsanStackTrace::GetCurrentPc() { - return GET_CALLER_PC(); -} +// ------------------ Interface -------------- {{{1 -void AsanStackTrace::FastUnwindStack(uptr pc, uptr bp) { - CHECK(size == 0 && trace[0] == pc); - size = 1; - if (!asan_inited) return; - AsanThread *t = asanThreadRegistry().GetCurrent(); - if (!t) return; - uptr *frame = (uptr*)bp; - uptr *prev_frame = frame; - uptr *top = (uptr*)t->stack_top(); - uptr *bottom = (uptr*)t->stack_bottom(); - while (frame >= prev_frame && - frame < top - 2 && - frame > bottom && - size < max_size) { - uptr pc1 = frame[1]; - if (pc1 != pc) { - trace[size++] = pc1; - } - prev_frame = frame; - frame = (uptr*)frame[0]; - } +// Provide default implementation of __asan_symbolize that does nothing +// and may be overriden by user if he wants to use his own symbolization. +// ASan on Windows has its own implementation of this. +#if !defined(_WIN32) && !SANITIZER_SUPPORTS_WEAK_HOOKS +SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE NOINLINE +bool __asan_symbolize(const void *pc, char *out_buffer, int out_size) { + return false; } - -// On 32-bits we don't compress stack traces. -// On 64-bits we compress stack traces: if a given pc differes slightly from -// the previous one, we record a 31-bit offset instead of the full pc. -uptr AsanStackTrace::CompressStack(AsanStackTrace *stack, - u32 *compressed, uptr size) { -#if __WORDSIZE == 32 - // Don't compress, just copy. - uptr res = 0; - for (uptr i = 0; i < stack->size && i < size; i++) { - compressed[i] = stack->trace[i]; - res++; - } - if (stack->size < size) - compressed[stack->size] = 0; -#else // 64 bits, compress. - uptr prev_pc = 0; - const uptr kMaxOffset = (1ULL << 30) - 1; - uptr c_index = 0; - uptr res = 0; - for (uptr i = 0, n = stack->size; i < n; i++) { - uptr pc = stack->trace[i]; - if (!pc) break; - if ((s64)pc < 0) break; - // Printf("C pc[%zu] %zx\n", i, pc); - if (prev_pc - pc < kMaxOffset || pc - prev_pc < kMaxOffset) { - uptr offset = (s64)(pc - prev_pc); - offset |= (1U << 31); - if (c_index >= size) break; - // Printf("C co[%zu] offset %zx\n", i, offset); - compressed[c_index++] = offset; - } else { - uptr hi = pc >> 32; - uptr lo = (pc << 32) >> 32; - CHECK((hi & (1 << 31)) == 0); - if (c_index + 1 >= size) break; - // Printf("C co[%zu] hi/lo: %zx %zx\n", c_index, hi, lo); - compressed[c_index++] = hi; - compressed[c_index++] = lo; - } - res++; - prev_pc = pc; - } - if (c_index < size) - compressed[c_index] = 0; - if (c_index + 1 < size) - compressed[c_index + 1] = 0; -#endif // __WORDSIZE - - // debug-only code -#if 0 - AsanStackTrace check_stack; - UncompressStack(&check_stack, compressed, size); - if (res < check_stack.size) { - Printf("res %zu check_stack.size %zu; c_size %zu\n", res, - check_stack.size, size); - } - // |res| may be greater than check_stack.size, because - // UncompressStack(CompressStack(stack)) eliminates the 0x0 frames. - CHECK(res >= check_stack.size); - CHECK(0 == REAL(memcmp)(check_stack.trace, stack->trace, - check_stack.size * sizeof(uptr))); #endif - - return res; -} - -void AsanStackTrace::UncompressStack(AsanStackTrace *stack, - u32 *compressed, uptr size) { -#if __WORDSIZE == 32 - // Don't uncompress, just copy. - stack->size = 0; - for (uptr i = 0; i < size && i < kStackTraceMax; i++) { - if (!compressed[i]) break; - stack->size++; - stack->trace[i] = compressed[i]; - } -#else // 64 bits, uncompress - uptr prev_pc = 0; - stack->size = 0; - for (uptr i = 0; i < size && stack->size < kStackTraceMax; i++) { - u32 x = compressed[i]; - uptr pc = 0; - if (x & (1U << 31)) { - // Printf("U co[%zu] offset: %x\n", i, x); - // this is an offset - s32 offset = x; - offset = (offset << 1) >> 1; // remove the 31-byte and sign-extend. - pc = prev_pc + offset; - CHECK(pc); - } else { - // CHECK(i + 1 < size); - if (i + 1 >= size) break; - uptr hi = x; - uptr lo = compressed[i+1]; - // Printf("U co[%zu] hi/lo: %zx %zx\n", i, hi, lo); - i++; - pc = (hi << 32) | lo; - if (!pc) break; - } - // Printf("U pc[%zu] %zx\n", stack->size, pc); - stack->trace[stack->size++] = pc; - prev_pc = pc; - } -#endif // __WORDSIZE -} - -} // namespace __asan diff --git a/lib/asan/asan_stack.h b/lib/asan/asan_stack.h index 6ca9a0b28bfc..46c9f3408725 100644 --- a/lib/asan/asan_stack.h +++ b/lib/asan/asan_stack.h @@ -14,91 +14,53 @@ #ifndef ASAN_STACK_H #define ASAN_STACK_H -#include "asan_internal.h" +#include "sanitizer_common/sanitizer_stacktrace.h" +#include "asan_flags.h" namespace __asan { -static const uptr kStackTraceMax = 64; - -struct AsanStackTrace { - uptr size; - uptr max_size; - uptr trace[kStackTraceMax]; - static void PrintStack(uptr *addr, uptr size); - void PrintStack() { - PrintStack(this->trace, this->size); - } - void CopyTo(uptr *dst, uptr dst_size) { - for (uptr i = 0; i < size && i < dst_size; i++) - dst[i] = trace[i]; - for (uptr i = size; i < dst_size; i++) - dst[i] = 0; - } - - void CopyFrom(uptr *src, uptr src_size) { - size = src_size; - if (size > kStackTraceMax) size = kStackTraceMax; - for (uptr i = 0; i < size; i++) { - trace[i] = src[i]; - } - } - - void GetStackTrace(uptr max_s, uptr pc, uptr bp); - - void FastUnwindStack(uptr pc, uptr bp); - - static uptr GetCurrentPc(); - - static uptr CompressStack(AsanStackTrace *stack, - u32 *compressed, uptr size); - static void UncompressStack(AsanStackTrace *stack, - u32 *compressed, uptr size); -}; +void GetStackTrace(StackTrace *stack, uptr max_s, uptr pc, uptr bp, bool fast); +void PrintStack(StackTrace *stack); } // namespace __asan -// Use this macro if you want to print stack trace with the caller -// of the current function in the top frame. -#define GET_CALLER_PC_BP_SP \ - uptr bp = GET_CURRENT_FRAME(); \ - uptr pc = GET_CALLER_PC(); \ - uptr local_stack; \ - uptr sp = (uptr)&local_stack - -// Use this macro if you want to print stack trace with the current -// function in the top frame. -#define GET_CURRENT_PC_BP_SP \ - uptr bp = GET_CURRENT_FRAME(); \ - uptr pc = AsanStackTrace::GetCurrentPc(); \ - uptr local_stack; \ - uptr sp = (uptr)&local_stack - // Get the stack trace with the given pc and bp. // The pc will be in the position 0 of the resulting stack trace. // The bp may refer to the current frame or to the caller's frame. // fast_unwind is currently unused. -#define GET_STACK_TRACE_WITH_PC_AND_BP(max_s, pc, bp) \ - AsanStackTrace stack; \ - stack.GetStackTrace(max_s, pc, bp) +#define GET_STACK_TRACE_WITH_PC_AND_BP(max_s, pc, bp, fast) \ + StackTrace stack; \ + GetStackTrace(&stack, max_s, pc, bp, fast) // NOTE: A Rule of thumb is to retrieve stack trace in the interceptors // as early as possible (in functions exposed to the user), as we generally // don't want stack trace to contain functions from ASan internals. -#define GET_STACK_TRACE_HERE(max_size) \ +#define GET_STACK_TRACE(max_size, fast) \ GET_STACK_TRACE_WITH_PC_AND_BP(max_size, \ - AsanStackTrace::GetCurrentPc(), GET_CURRENT_FRAME()) + StackTrace::GetCurrentPc(), GET_CURRENT_FRAME(), fast) + +#define GET_STACK_TRACE_FATAL(pc, bp) \ + GET_STACK_TRACE_WITH_PC_AND_BP(kStackTraceMax, pc, bp, \ + flags()->fast_unwind_on_fatal) + +#define GET_STACK_TRACE_FATAL_HERE \ + GET_STACK_TRACE(kStackTraceMax, flags()->fast_unwind_on_fatal) + +#define GET_STACK_TRACE_THREAD \ + GET_STACK_TRACE(kStackTraceMax, true) -#define GET_STACK_TRACE_HERE_FOR_MALLOC \ - GET_STACK_TRACE_HERE(flags()->malloc_context_size) +#define GET_STACK_TRACE_MALLOC \ + GET_STACK_TRACE(flags()->malloc_context_size, \ + flags()->fast_unwind_on_malloc) -#define GET_STACK_TRACE_HERE_FOR_FREE(ptr) \ - GET_STACK_TRACE_HERE(flags()->malloc_context_size) +#define GET_STACK_TRACE_FREE GET_STACK_TRACE_MALLOC #define PRINT_CURRENT_STACK() \ { \ - GET_STACK_TRACE_HERE(kStackTraceMax); \ - stack.PrintStack(); \ + GET_STACK_TRACE(kStackTraceMax, \ + flags()->fast_unwind_on_fatal); \ + PrintStack(&stack); \ } #endif // ASAN_STACK_H diff --git a/lib/asan/asan_stats.cc b/lib/asan/asan_stats.cc index ef5e53a8bf33..c57c8cc61aed 100644 --- a/lib/asan/asan_stats.cc +++ b/lib/asan/asan_stats.cc @@ -12,11 +12,11 @@ // Code related to statistics collected by AddressSanitizer. //===----------------------------------------------------------------------===// #include "asan_interceptors.h" -#include "asan_interface.h" #include "asan_internal.h" -#include "asan_lock.h" #include "asan_stats.h" #include "asan_thread_registry.h" +#include "sanitizer/asan_interface.h" +#include "sanitizer_common/sanitizer_stackdepot.h" namespace __asan { @@ -27,39 +27,45 @@ AsanStats::AsanStats() { static void PrintMallocStatsArray(const char *prefix, uptr (&array)[kNumberOfSizeClasses]) { - AsanPrintf("%s", prefix); + Printf("%s", prefix); for (uptr i = 0; i < kNumberOfSizeClasses; i++) { if (!array[i]) continue; - AsanPrintf("%zu:%zu; ", i, array[i]); + Printf("%zu:%zu; ", i, array[i]); } - AsanPrintf("\n"); + Printf("\n"); } void AsanStats::Print() { - AsanPrintf("Stats: %zuM malloced (%zuM for red zones) by %zu calls\n", + Printf("Stats: %zuM malloced (%zuM for red zones) by %zu calls\n", malloced>>20, malloced_redzones>>20, mallocs); - AsanPrintf("Stats: %zuM realloced by %zu calls\n", realloced>>20, reallocs); - AsanPrintf("Stats: %zuM freed by %zu calls\n", freed>>20, frees); - AsanPrintf("Stats: %zuM really freed by %zu calls\n", + Printf("Stats: %zuM realloced by %zu calls\n", realloced>>20, reallocs); + Printf("Stats: %zuM freed by %zu calls\n", freed>>20, frees); + Printf("Stats: %zuM really freed by %zu calls\n", really_freed>>20, real_frees); - AsanPrintf("Stats: %zuM (%zu full pages) mmaped in %zu calls\n", - mmaped>>20, mmaped / kPageSize, mmaps); + Printf("Stats: %zuM (%zuM-%zuM) mmaped; %zu maps, %zu unmaps\n", + (mmaped-munmaped)>>20, mmaped>>20, munmaped>>20, + mmaps, munmaps); PrintMallocStatsArray(" mmaps by size class: ", mmaped_by_size); PrintMallocStatsArray(" mallocs by size class: ", malloced_by_size); PrintMallocStatsArray(" frees by size class: ", freed_by_size); PrintMallocStatsArray(" rfrees by size class: ", really_freed_by_size); - AsanPrintf("Stats: malloc large: %zu small slow: %zu\n", + Printf("Stats: malloc large: %zu small slow: %zu\n", malloc_large, malloc_small_slow); } -static AsanLock print_lock(LINKER_INITIALIZED); +static BlockingMutex print_lock(LINKER_INITIALIZED); static void PrintAccumulatedStats() { - AsanStats stats = asanThreadRegistry().GetAccumulatedStats(); + AsanStats stats; + asanThreadRegistry().GetAccumulatedStats(&stats); // Use lock to keep reports from mixing up. - ScopedLock lock(&print_lock); + BlockingMutexLock lock(&print_lock); stats.Print(); + StackDepotStats *stack_depot_stats = StackDepotGetStats(); + Printf("Stats: StackDepot: %zd ids; %zdM mapped\n", + stack_depot_stats->n_uniq_ids, stack_depot_stats->mapped >> 20); + PrintInternalAllocatorStats(); } } // namespace __asan diff --git a/lib/asan/asan_stats.h b/lib/asan/asan_stats.h index b4c63f44fc68..37846bc92ad2 100644 --- a/lib/asan/asan_stats.h +++ b/lib/asan/asan_stats.h @@ -37,6 +37,8 @@ struct AsanStats { uptr realloced; uptr mmaps; uptr mmaped; + uptr munmaps; + uptr munmaped; uptr mmaped_by_size[kNumberOfSizeClasses]; uptr malloced_by_size[kNumberOfSizeClasses]; uptr freed_by_size[kNumberOfSizeClasses]; @@ -54,6 +56,14 @@ struct AsanStats { void Print(); }; +// A cross-platform equivalent of malloc_statistics_t on Mac OS. +struct AsanMallocStats { + uptr blocks_in_use; + uptr size_in_use; + uptr max_size_in_use; + uptr size_allocated; +}; + } // namespace __asan #endif // ASAN_STATS_H diff --git a/lib/asan/asan_thread.cc b/lib/asan/asan_thread.cc index 05a41ea9685a..778e91932ed5 100644 --- a/lib/asan/asan_thread.cc +++ b/lib/asan/asan_thread.cc @@ -26,24 +26,18 @@ AsanThread::AsanThread(LinkerInitialized x) malloc_storage_(x), stats_(x) { } -static AsanLock mu_for_thread_summary(LINKER_INITIALIZED); -static LowLevelAllocator allocator_for_thread_summary(LINKER_INITIALIZED); - AsanThread *AsanThread::Create(u32 parent_tid, thread_callback_t start_routine, - void *arg, AsanStackTrace *stack) { - uptr size = RoundUpTo(sizeof(AsanThread), kPageSize); + void *arg, StackTrace *stack) { + uptr PageSize = GetPageSizeCached(); + uptr size = RoundUpTo(sizeof(AsanThread), PageSize); AsanThread *thread = (AsanThread*)MmapOrDie(size, __FUNCTION__); thread->start_routine_ = start_routine; thread->arg_ = arg; - const uptr kSummaryAllocSize = 1024; + const uptr kSummaryAllocSize = PageSize; CHECK_LE(sizeof(AsanThreadSummary), kSummaryAllocSize); - AsanThreadSummary *summary; - { - ScopedLock lock(&mu_for_thread_summary); - summary = (AsanThreadSummary*) - allocator_for_thread_summary.Allocate(kSummaryAllocSize); - } + AsanThreadSummary *summary = + (AsanThreadSummary*)MmapOrDie(PageSize, "AsanThreadSummary"); summary->Init(parent_tid, stack); summary->set_thread(thread); thread->set_summary(summary); @@ -73,14 +67,14 @@ void AsanThread::Destroy() { // and we don't want it to have any poisoned stack. ClearShadowForThreadStack(); fake_stack().Cleanup(); - uptr size = RoundUpTo(sizeof(AsanThread), kPageSize); + uptr size = RoundUpTo(sizeof(AsanThread), GetPageSizeCached()); UnmapOrDie(this, size); } void AsanThread::Init() { SetThreadStackTopAndBottom(); CHECK(AddrIsInMem(stack_bottom_)); - CHECK(AddrIsInMem(stack_top_)); + CHECK(AddrIsInMem(stack_top_ - 1)); ClearShadowForThreadStack(); if (flags()->verbosity >= 1) { int local = 0; @@ -125,25 +119,25 @@ void AsanThread::ClearShadowForThreadStack() { const char *AsanThread::GetFrameNameByAddr(uptr addr, uptr *offset) { uptr bottom = 0; - bool is_fake_stack = false; if (AddrIsInStack(addr)) { bottom = stack_bottom(); } else { bottom = fake_stack().AddrIsInFakeStack(addr); CHECK(bottom); - is_fake_stack = true; + *offset = addr - bottom; + return (const char *)((uptr*)bottom)[1]; } - uptr aligned_addr = addr & ~(__WORDSIZE/8 - 1); // align addr. + uptr aligned_addr = addr & ~(SANITIZER_WORDSIZE/8 - 1); // align addr. u8 *shadow_ptr = (u8*)MemToShadow(aligned_addr); u8 *shadow_bottom = (u8*)MemToShadow(bottom); while (shadow_ptr >= shadow_bottom && - *shadow_ptr != kAsanStackLeftRedzoneMagic) { + *shadow_ptr != kAsanStackLeftRedzoneMagic) { shadow_ptr--; } while (shadow_ptr >= shadow_bottom && - *shadow_ptr == kAsanStackLeftRedzoneMagic) { + *shadow_ptr == kAsanStackLeftRedzoneMagic) { shadow_ptr--; } @@ -153,8 +147,7 @@ const char *AsanThread::GetFrameNameByAddr(uptr addr, uptr *offset) { } uptr* ptr = (uptr*)SHADOW_TO_MEM((uptr)(shadow_ptr + 1)); - CHECK((ptr[0] == kCurrentStackFrameMagic) || - (is_fake_stack && ptr[0] == kRetiredStackFrameMagic)); + CHECK(ptr[0] == kCurrentStackFrameMagic); *offset = addr - (uptr)ptr; return (const char*)ptr[1]; } diff --git a/lib/asan/asan_thread.h b/lib/asan/asan_thread.h index 9a032fe3e66c..acc27e52e224 100644 --- a/lib/asan/asan_thread.h +++ b/lib/asan/asan_thread.h @@ -31,7 +31,7 @@ class AsanThread; class AsanThreadSummary { public: explicit AsanThreadSummary(LinkerInitialized) { } // for T0. - void Init(u32 parent_tid, AsanStackTrace *stack) { + void Init(u32 parent_tid, StackTrace *stack) { parent_tid_ = parent_tid; announced_ = false; tid_ = kInvalidTid; @@ -39,35 +39,40 @@ class AsanThreadSummary { internal_memcpy(&stack_, stack, sizeof(*stack)); } thread_ = 0; - } - void Announce() { - if (tid_ == 0) return; // no need to announce the main thread. - if (!announced_) { - announced_ = true; - AsanPrintf("Thread T%d created by T%d here:\n", tid_, parent_tid_); - stack_.PrintStack(); - } + name_[0] = 0; } u32 tid() { return tid_; } void set_tid(u32 tid) { tid_ = tid; } + u32 parent_tid() { return parent_tid_; } + bool announced() { return announced_; } + void set_announced(bool announced) { announced_ = announced; } + StackTrace *stack() { return &stack_; } AsanThread *thread() { return thread_; } void set_thread(AsanThread *thread) { thread_ = thread; } static void TSDDtor(void *tsd); + void set_name(const char *name) { + internal_strncpy(name_, name, sizeof(name_) - 1); + } + const char *name() { return name_; } private: u32 tid_; u32 parent_tid_; bool announced_; - AsanStackTrace stack_; + StackTrace stack_; AsanThread *thread_; + char name_[128]; }; +// AsanThreadSummary objects are never freed, so we need many of them. +COMPILER_CHECK(sizeof(AsanThreadSummary) <= 4094); + // AsanThread are stored in TSD and destroyed when the thread dies. class AsanThread { public: explicit AsanThread(LinkerInitialized); // for T0. static AsanThread *Create(u32 parent_tid, thread_callback_t start_routine, - void *arg, AsanStackTrace *stack); + void *arg, StackTrace *stack); void Destroy(); void Init(); // Should be called from the thread itself. @@ -91,7 +96,6 @@ class AsanThread { AsanStats &stats() { return stats_; } private: - void SetThreadStackTopAndBottom(); void ClearShadowForThreadStack(); AsanThreadSummary *summary_; diff --git a/lib/asan/asan_thread_registry.cc b/lib/asan/asan_thread_registry.cc index 4540d589c552..80675405fbd5 100644 --- a/lib/asan/asan_thread_registry.cc +++ b/lib/asan/asan_thread_registry.cc @@ -20,7 +20,7 @@ namespace __asan { -static AsanThreadRegistry asan_thread_registry(__asan::LINKER_INITIALIZED); +static AsanThreadRegistry asan_thread_registry(LINKER_INITIALIZED); AsanThreadRegistry &asanThreadRegistry() { return asan_thread_registry; @@ -30,6 +30,7 @@ AsanThreadRegistry::AsanThreadRegistry(LinkerInitialized x) : main_thread_(x), main_thread_summary_(x), accumulated_stats_(x), + max_malloced_memory_(x), mu_(x) { } void AsanThreadRegistry::Init() { @@ -43,7 +44,7 @@ void AsanThreadRegistry::Init() { } void AsanThreadRegistry::RegisterThread(AsanThread *thread) { - ScopedLock lock(&mu_); + BlockingMutexLock lock(&mu_); u32 tid = n_threads_; n_threads_++; CHECK(n_threads_ < kMaxNumberOfThreads); @@ -55,7 +56,7 @@ void AsanThreadRegistry::RegisterThread(AsanThread *thread) { } void AsanThreadRegistry::UnregisterThread(AsanThread *thread) { - ScopedLock lock(&mu_); + BlockingMutexLock lock(&mu_); FlushToAccumulatedStatsUnlocked(&thread->stats()); AsanThreadSummary *summary = thread->summary(); CHECK(summary); @@ -69,7 +70,7 @@ AsanThread *AsanThreadRegistry::GetMain() { AsanThread *AsanThreadRegistry::GetCurrent() { AsanThreadSummary *summary = (AsanThreadSummary *)AsanTSDGet(); if (!summary) { -#ifdef ANDROID +#if ASAN_ANDROID // On Android, libc constructor is called _after_ asan_init, and cleans up // TSD. Try to figure out if this is still the main thread by the stack // address. We are not entirely sure that we have correct main thread @@ -103,32 +104,51 @@ AsanStats &AsanThreadRegistry::GetCurrentThreadStats() { return (t) ? t->stats() : main_thread_.stats(); } -AsanStats AsanThreadRegistry::GetAccumulatedStats() { - ScopedLock lock(&mu_); +void AsanThreadRegistry::GetAccumulatedStats(AsanStats *stats) { + BlockingMutexLock lock(&mu_); UpdateAccumulatedStatsUnlocked(); - return accumulated_stats_; + internal_memcpy(stats, &accumulated_stats_, sizeof(accumulated_stats_)); } uptr AsanThreadRegistry::GetCurrentAllocatedBytes() { - ScopedLock lock(&mu_); + BlockingMutexLock lock(&mu_); UpdateAccumulatedStatsUnlocked(); - return accumulated_stats_.malloced - accumulated_stats_.freed; + uptr malloced = accumulated_stats_.malloced; + uptr freed = accumulated_stats_.freed; + // Return sane value if malloced < freed due to racy + // way we update accumulated stats. + return (malloced > freed) ? malloced - freed : 1; } uptr AsanThreadRegistry::GetHeapSize() { - ScopedLock lock(&mu_); + BlockingMutexLock lock(&mu_); UpdateAccumulatedStatsUnlocked(); - return accumulated_stats_.mmaped; + return accumulated_stats_.mmaped - accumulated_stats_.munmaped; } uptr AsanThreadRegistry::GetFreeBytes() { - ScopedLock lock(&mu_); + BlockingMutexLock lock(&mu_); UpdateAccumulatedStatsUnlocked(); - return accumulated_stats_.mmaped - - accumulated_stats_.malloced - - accumulated_stats_.malloced_redzones - + accumulated_stats_.really_freed - + accumulated_stats_.really_freed_redzones; + uptr total_free = accumulated_stats_.mmaped + - accumulated_stats_.munmaped + + accumulated_stats_.really_freed + + accumulated_stats_.really_freed_redzones; + uptr total_used = accumulated_stats_.malloced + + accumulated_stats_.malloced_redzones; + // Return sane value if total_free < total_used due to racy + // way we update accumulated stats. + return (total_free > total_used) ? total_free - total_used : 1; +} + +// Return several stats counters with a single call to +// UpdateAccumulatedStatsUnlocked(). +void AsanThreadRegistry::FillMallocStatistics(AsanMallocStats *malloc_stats) { + BlockingMutexLock lock(&mu_); + UpdateAccumulatedStatsUnlocked(); + malloc_stats->blocks_in_use = accumulated_stats_.mallocs; + malloc_stats->size_in_use = accumulated_stats_.malloced; + malloc_stats->max_size_in_use = max_malloced_memory_; + malloc_stats->size_allocated = accumulated_stats_.mmaped; } AsanThreadSummary *AsanThreadRegistry::FindByTid(u32 tid) { @@ -138,7 +158,7 @@ AsanThreadSummary *AsanThreadRegistry::FindByTid(u32 tid) { } AsanThread *AsanThreadRegistry::FindThreadByStackAddress(uptr addr) { - ScopedLock lock(&mu_); + BlockingMutexLock lock(&mu_); for (u32 tid = 0; tid < n_threads_; tid++) { AsanThread *t = thread_summaries_[tid]->thread(); if (!t || !(t->fake_stack().StackSize())) continue; @@ -156,6 +176,12 @@ void AsanThreadRegistry::UpdateAccumulatedStatsUnlocked() { FlushToAccumulatedStatsUnlocked(&t->stats()); } } + // This is not very accurate: we may miss allocation peaks that happen + // between two updates of accumulated_stats_. For more accurate bookkeeping + // the maximum should be updated on every malloc(), which is unacceptable. + if (max_malloced_memory_ < accumulated_stats_.malloced) { + max_malloced_memory_ = accumulated_stats_.malloced; + } } void AsanThreadRegistry::FlushToAccumulatedStatsUnlocked(AsanStats *stats) { diff --git a/lib/asan/asan_thread_registry.h b/lib/asan/asan_thread_registry.h index 7037b9edc161..adb1a6d4f32d 100644 --- a/lib/asan/asan_thread_registry.h +++ b/lib/asan/asan_thread_registry.h @@ -15,10 +15,10 @@ #ifndef ASAN_THREAD_REGISTRY_H #define ASAN_THREAD_REGISTRY_H -#include "asan_lock.h" #include "asan_stack.h" #include "asan_stats.h" #include "asan_thread.h" +#include "sanitizer_common/sanitizer_mutex.h" namespace __asan { @@ -47,12 +47,13 @@ class AsanThreadRegistry { // Returns stats for GetCurrent(), or stats for // T0 if GetCurrent() returns 0. AsanStats &GetCurrentThreadStats(); - // Flushes all thread-local stats to accumulated stats, and returns + // Flushes all thread-local stats to accumulated stats, and makes // a copy of accumulated stats. - AsanStats GetAccumulatedStats(); + void GetAccumulatedStats(AsanStats *stats); uptr GetCurrentAllocatedBytes(); uptr GetHeapSize(); uptr GetFreeBytes(); + void FillMallocStatistics(AsanMallocStats *malloc_stats); AsanThreadSummary *FindByTid(u32 tid); AsanThread *FindThreadByStackAddress(uptr addr); @@ -68,8 +69,11 @@ class AsanThreadRegistry { AsanThread main_thread_; AsanThreadSummary main_thread_summary_; AsanStats accumulated_stats_; + // Required for malloc_zone_statistics() on OS X. This can't be stored in + // per-thread AsanStats. + uptr max_malloced_memory_; u32 n_threads_; - AsanLock mu_; + BlockingMutex mu_; bool inited_; }; diff --git a/lib/asan/asan_win.cc b/lib/asan/asan_win.cc index 9e899d5865fa..d8ce050641bc 100644 --- a/lib/asan/asan_win.cc +++ b/lib/asan/asan_win.cc @@ -17,30 +17,29 @@ #include <dbghelp.h> #include <stdlib.h> -#include <new> // FIXME: temporarily needed for placement new in AsanLock. - #include "asan_interceptors.h" #include "asan_internal.h" -#include "asan_lock.h" #include "asan_thread.h" #include "sanitizer_common/sanitizer_libc.h" +#include "sanitizer_common/sanitizer_mutex.h" namespace __asan { // ---------------------- Stacktraces, symbols, etc. ---------------- {{{1 -static AsanLock dbghelp_lock(LINKER_INITIALIZED); +static BlockingMutex dbghelp_lock(LINKER_INITIALIZED); static bool dbghelp_initialized = false; #pragma comment(lib, "dbghelp.lib") -void AsanStackTrace::GetStackTrace(uptr max_s, uptr pc, uptr bp) { - max_size = max_s; +void GetStackTrace(StackTrace *stack, uptr max_s, uptr pc, uptr bp, bool fast) { + (void)fast; + stack->max_size = max_s; void *tmp[kStackTraceMax]; // FIXME: CaptureStackBackTrace might be too slow for us. // FIXME: Compare with StackWalk64. // FIXME: Look at LLVMUnhandledExceptionFilter in Signals.inc - uptr cs_ret = CaptureStackBackTrace(1, max_size, tmp, 0), - offset = 0; + uptr cs_ret = CaptureStackBackTrace(1, stack->max_size, tmp, 0); + uptr offset = 0; // Skip the RTL frames by searching for the PC in the stacktrace. // FIXME: this doesn't work well for the malloc/free stacks yet. for (uptr i = 0; i < cs_ret; i++) { @@ -50,86 +49,9 @@ void AsanStackTrace::GetStackTrace(uptr max_s, uptr pc, uptr bp) { break; } - size = cs_ret - offset; - for (uptr i = 0; i < size; i++) - trace[i] = (uptr)tmp[i + offset]; -} - -bool __asan_WinSymbolize(const void *addr, char *out_buffer, int buffer_size) { - ScopedLock lock(&dbghelp_lock); - if (!dbghelp_initialized) { - SymSetOptions(SYMOPT_DEFERRED_LOADS | - SYMOPT_UNDNAME | - SYMOPT_LOAD_LINES); - CHECK(SymInitialize(GetCurrentProcess(), 0, TRUE)); - // FIXME: We don't call SymCleanup() on exit yet - should we? - dbghelp_initialized = true; - } - - // See http://msdn.microsoft.com/en-us/library/ms680578(VS.85).aspx - char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(CHAR)]; - PSYMBOL_INFO symbol = (PSYMBOL_INFO)buffer; - symbol->SizeOfStruct = sizeof(SYMBOL_INFO); - symbol->MaxNameLen = MAX_SYM_NAME; - DWORD64 offset = 0; - BOOL got_objname = SymFromAddr(GetCurrentProcess(), - (DWORD64)addr, &offset, symbol); - if (!got_objname) - return false; - - DWORD unused; - IMAGEHLP_LINE64 info; - info.SizeOfStruct = sizeof(IMAGEHLP_LINE64); - BOOL got_fileline = SymGetLineFromAddr64(GetCurrentProcess(), - (DWORD64)addr, &unused, &info); - int written = 0; - out_buffer[0] = '\0'; - // FIXME: it might be useful to print out 'obj' or 'obj+offset' info too. - if (got_fileline) { - written += internal_snprintf(out_buffer + written, buffer_size - written, - " %s %s:%d", symbol->Name, - info.FileName, info.LineNumber); - } else { - written += internal_snprintf(out_buffer + written, buffer_size - written, - " %s+0x%p", symbol->Name, offset); - } - return true; -} - -// ---------------------- AsanLock ---------------- {{{1 -enum LockState { - LOCK_UNINITIALIZED = 0, - LOCK_READY = -1, -}; - -AsanLock::AsanLock(LinkerInitialized li) { - // FIXME: see comments in AsanLock::Lock() for the details. - CHECK(li == LINKER_INITIALIZED || owner_ == LOCK_UNINITIALIZED); - - CHECK(sizeof(CRITICAL_SECTION) <= sizeof(opaque_storage_)); - InitializeCriticalSection((LPCRITICAL_SECTION)opaque_storage_); - owner_ = LOCK_READY; -} - -void AsanLock::Lock() { - if (owner_ == LOCK_UNINITIALIZED) { - // FIXME: hm, global AsanLock objects are not initialized?!? - // This might be a side effect of the clang+cl+link Frankenbuild... - new(this) AsanLock((LinkerInitialized)(LINKER_INITIALIZED + 1)); - - // FIXME: If it turns out the linker doesn't invoke our - // constructors, we should probably manually Lock/Unlock all the global - // locks while we're starting in one thread to avoid double-init races. - } - EnterCriticalSection((LPCRITICAL_SECTION)opaque_storage_); - CHECK(owner_ == LOCK_READY); - owner_ = GetThreadSelf(); -} - -void AsanLock::Unlock() { - CHECK(owner_ == GetThreadSelf()); - owner_ = LOCK_READY; - LeaveCriticalSection((LPCRITICAL_SECTION)opaque_storage_); + stack->size = cs_ret - offset; + for (uptr i = 0; i < stack->size; i++) + stack->trace[i] = (uptr)tmp[i + offset]; } // ---------------------- TSD ---------------- {{{1 @@ -153,6 +75,10 @@ void AsanTSDSet(void *tsd) { } // ---------------------- Various stuff ---------------- {{{1 +void MaybeReexec() { + // No need to re-exec on Windows. +} + void *AsanDoesNotSupportStaticLinkage() { #if defined(_DEBUG) #error Please build the runtime with a non-debug CRT: /MD or /MT @@ -176,6 +102,58 @@ void AsanPlatformThreadInit() { // Nothing here for now. } +void ReadContextStack(void *context, uptr *stack, uptr *ssize) { + UNIMPLEMENTED(); +} + } // namespace __asan +// ---------------------- Interface ---------------- {{{1 +using namespace __asan; // NOLINT + +extern "C" { +SANITIZER_INTERFACE_ATTRIBUTE NOINLINE +bool __asan_symbolize(const void *addr, char *out_buffer, int buffer_size) { + BlockingMutexLock lock(&dbghelp_lock); + if (!dbghelp_initialized) { + SymSetOptions(SYMOPT_DEFERRED_LOADS | + SYMOPT_UNDNAME | + SYMOPT_LOAD_LINES); + CHECK(SymInitialize(GetCurrentProcess(), 0, TRUE)); + // FIXME: We don't call SymCleanup() on exit yet - should we? + dbghelp_initialized = true; + } + + // See http://msdn.microsoft.com/en-us/library/ms680578(VS.85).aspx + char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(CHAR)]; + PSYMBOL_INFO symbol = (PSYMBOL_INFO)buffer; + symbol->SizeOfStruct = sizeof(SYMBOL_INFO); + symbol->MaxNameLen = MAX_SYM_NAME; + DWORD64 offset = 0; + BOOL got_objname = SymFromAddr(GetCurrentProcess(), + (DWORD64)addr, &offset, symbol); + if (!got_objname) + return false; + + DWORD unused; + IMAGEHLP_LINE64 info; + info.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + BOOL got_fileline = SymGetLineFromAddr64(GetCurrentProcess(), + (DWORD64)addr, &unused, &info); + int written = 0; + out_buffer[0] = '\0'; + // FIXME: it might be useful to print out 'obj' or 'obj+offset' info too. + if (got_fileline) { + written += internal_snprintf(out_buffer + written, buffer_size - written, + " %s %s:%d", symbol->Name, + info.FileName, info.LineNumber); + } else { + written += internal_snprintf(out_buffer + written, buffer_size - written, + " %s+0x%p", symbol->Name, offset); + } + return true; +} +} // extern "C" + + #endif // _WIN32 diff --git a/lib/asan/dynamic/Makefile.mk b/lib/asan/dynamic/Makefile.mk new file mode 100644 index 000000000000..897844e7eed4 --- /dev/null +++ b/lib/asan/dynamic/Makefile.mk @@ -0,0 +1,25 @@ +#===- lib/asan/dynamic/Makefile.mk -------------------------*- Makefile -*--===# +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +#===------------------------------------------------------------------------===# + +ModuleName := asan_dynamic +SubDirs := + +Sources := $(foreach file,$(wildcard $(Dir)/*.cc),$(notdir $(file))) +ObjNames := $(Sources:%.cc=%.o) + +Implementation := Generic + +# FIXME: use automatic dependencies? +Dependencies := $(wildcard $(Dir)/*.h) +Dependencies += $(wildcard $(Dir)/../../interception/*.h) +Dependencies += $(wildcard $(Dir)/../../interception/mach_override/*.h) +Dependencies += $(wildcard $(Dir)/../../sanitizer_common/*.h) + +# Define a convenience variable for the asan dynamic functions. +AsanDynamicFunctions := $(Sources:%.cc=%) diff --git a/lib/asan/dynamic/asan_interceptors_dynamic.cc b/lib/asan/dynamic/asan_interceptors_dynamic.cc new file mode 100644 index 000000000000..4f0f7bd2d5f8 --- /dev/null +++ b/lib/asan/dynamic/asan_interceptors_dynamic.cc @@ -0,0 +1,111 @@ +//===-- asan_interceptors_dynamic.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. +// +// __DATA,__interpose section of the dynamic runtime library for Mac OS. +//===----------------------------------------------------------------------===// + +#if defined(__APPLE__) + +#include "../asan_interceptors.h" +#include "../asan_intercepted_functions.h" + +namespace __asan { + +#if !MAC_INTERPOSE_FUNCTIONS +# error \ + Dynamic interposing library should be built with -DMAC_INTERPOSE_FUNCTIONS +#endif + +#define INTERPOSE_FUNCTION(function) \ + { reinterpret_cast<const uptr>(WRAP(function)), \ + reinterpret_cast<const uptr>(function) } + +#define INTERPOSE_FUNCTION_2(function, wrapper) \ + { reinterpret_cast<const uptr>(wrapper), \ + reinterpret_cast<const uptr>(function) } + +struct interpose_substitution { + const uptr replacement; + const uptr original; +}; + +__attribute__((used)) +const interpose_substitution substitutions[] + __attribute__((section("__DATA, __interpose"))) = { + INTERPOSE_FUNCTION(strlen), + INTERPOSE_FUNCTION(memcmp), + INTERPOSE_FUNCTION(memcpy), + INTERPOSE_FUNCTION(memmove), + INTERPOSE_FUNCTION(memset), + INTERPOSE_FUNCTION(strchr), + INTERPOSE_FUNCTION(strcat), + INTERPOSE_FUNCTION(strncat), + INTERPOSE_FUNCTION(strcpy), + INTERPOSE_FUNCTION(strncpy), + INTERPOSE_FUNCTION(pthread_create), + INTERPOSE_FUNCTION(longjmp), +#if ASAN_INTERCEPT__LONGJMP + INTERPOSE_FUNCTION(_longjmp), +#endif +#if ASAN_INTERCEPT_SIGLONGJMP + INTERPOSE_FUNCTION(siglongjmp), +#endif +#if ASAN_INTERCEPT_STRDUP + INTERPOSE_FUNCTION(strdup), +#endif +#if ASAN_INTERCEPT_STRNLEN + INTERPOSE_FUNCTION(strnlen), +#endif +#if ASAN_INTERCEPT_INDEX + INTERPOSE_FUNCTION_2(index, WRAP(strchr)), +#endif + INTERPOSE_FUNCTION(strcmp), + INTERPOSE_FUNCTION(strncmp), +#if ASAN_INTERCEPT_STRCASECMP_AND_STRNCASECMP + INTERPOSE_FUNCTION(strcasecmp), + INTERPOSE_FUNCTION(strncasecmp), +#endif + INTERPOSE_FUNCTION(atoi), + INTERPOSE_FUNCTION(atol), + INTERPOSE_FUNCTION(strtol), +#if ASAN_INTERCEPT_ATOLL_AND_STRTOLL + INTERPOSE_FUNCTION(atoll), + INTERPOSE_FUNCTION(strtoll), +#endif +#if ASAN_INTERCEPT_MLOCKX + INTERPOSE_FUNCTION(mlock), + INTERPOSE_FUNCTION(munlock), + INTERPOSE_FUNCTION(mlockall), + INTERPOSE_FUNCTION(munlockall), +#endif + INTERPOSE_FUNCTION(dispatch_async_f), + INTERPOSE_FUNCTION(dispatch_sync_f), + INTERPOSE_FUNCTION(dispatch_after_f), + INTERPOSE_FUNCTION(dispatch_barrier_async_f), + INTERPOSE_FUNCTION(dispatch_group_async_f), +#ifndef MISSING_BLOCKS_SUPPORT + INTERPOSE_FUNCTION(dispatch_group_async), + INTERPOSE_FUNCTION(dispatch_async), + INTERPOSE_FUNCTION(dispatch_after), + INTERPOSE_FUNCTION(dispatch_source_set_event_handler), + INTERPOSE_FUNCTION(dispatch_source_set_cancel_handler), +#endif + INTERPOSE_FUNCTION(signal), + INTERPOSE_FUNCTION(sigaction), + + INTERPOSE_FUNCTION(__CFInitialize), + INTERPOSE_FUNCTION(CFStringCreateCopy), + INTERPOSE_FUNCTION(free), +}; + +} // namespace __asan + +#endif // __APPLE__ diff --git a/lib/asan/lit_tests/CMakeLists.txt b/lib/asan/lit_tests/CMakeLists.txt new file mode 100644 index 000000000000..1609032d4670 --- /dev/null +++ b/lib/asan/lit_tests/CMakeLists.txt @@ -0,0 +1,32 @@ +set(ASAN_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/..) +set(ASAN_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/..) + +configure_lit_site_cfg( + ${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.in + ${CMAKE_CURRENT_BINARY_DIR}/lit.site.cfg + ) + +configure_lit_site_cfg( + ${CMAKE_CURRENT_SOURCE_DIR}/Unit/lit.site.cfg.in + ${CMAKE_CURRENT_BINARY_DIR}/Unit/lit.site.cfg + ) + +if(COMPILER_RT_CAN_EXECUTE_TESTS) + # Run ASan tests only if we're sure we may produce working binaries. + set(ASAN_TEST_DEPS + clang clang-headers FileCheck count not llvm-nm llvm-symbolizer + ${ASAN_RUNTIME_LIBRARIES} + ) + set(ASAN_TEST_PARAMS + asan_site_config=${CMAKE_CURRENT_BINARY_DIR}/lit.site.cfg + ) + if(LLVM_INCLUDE_TESTS) + list(APPEND ASAN_TEST_DEPS AsanUnitTests) + endif() + add_lit_testsuite(check-asan "Running the AddressSanitizer tests" + ${CMAKE_CURRENT_BINARY_DIR} + PARAMS ${ASAN_TEST_PARAMS} + DEPENDS ${ASAN_TEST_DEPS} + ) + set_target_properties(check-asan PROPERTIES FOLDER "ASan tests") +endif() diff --git a/lib/asan/lit_tests/Helpers/blacklist-extra.cc b/lib/asan/lit_tests/Helpers/blacklist-extra.cc new file mode 100644 index 000000000000..627115cdda2b --- /dev/null +++ b/lib/asan/lit_tests/Helpers/blacklist-extra.cc @@ -0,0 +1,5 @@ +// This function is broken, but this file is blacklisted +int externalBrokenFunction(int argc) { + char x[10] = {0}; + return x[argc * 10]; // BOOM +} diff --git a/lib/asan/lit_tests/Helpers/initialization-blacklist-extra.cc b/lib/asan/lit_tests/Helpers/initialization-blacklist-extra.cc new file mode 100644 index 000000000000..09aed2112d5e --- /dev/null +++ b/lib/asan/lit_tests/Helpers/initialization-blacklist-extra.cc @@ -0,0 +1,15 @@ +int zero_init() { return 0; } +int badGlobal = zero_init(); +int readBadGlobal() { return badGlobal; } + +namespace badNamespace { +class BadClass { + public: + BadClass() { value = 0; } + int value; +}; +// Global object with non-trivial constructor. +BadClass bad_object; +} // namespace badNamespace + +int accessBadObject() { return badNamespace::bad_object.value; } diff --git a/lib/asan/lit_tests/Helpers/initialization-blacklist.txt b/lib/asan/lit_tests/Helpers/initialization-blacklist.txt new file mode 100644 index 000000000000..c5f6610937f0 --- /dev/null +++ b/lib/asan/lit_tests/Helpers/initialization-blacklist.txt @@ -0,0 +1,2 @@ +global-init:*badGlobal* +global-init-type:*badNamespace::BadClass* diff --git a/lib/asan/lit_tests/Helpers/initialization-bug-extra.cc b/lib/asan/lit_tests/Helpers/initialization-bug-extra.cc new file mode 100644 index 000000000000..3c4cb411defa --- /dev/null +++ b/lib/asan/lit_tests/Helpers/initialization-bug-extra.cc @@ -0,0 +1,5 @@ +// This file simply declares a dynamically initialized var by the name of 'y'. +int initY() { + return 5; +} +int y = initY(); diff --git a/lib/asan/lit_tests/Helpers/initialization-bug-extra2.cc b/lib/asan/lit_tests/Helpers/initialization-bug-extra2.cc new file mode 100644 index 000000000000..a3d8f190e58b --- /dev/null +++ b/lib/asan/lit_tests/Helpers/initialization-bug-extra2.cc @@ -0,0 +1,6 @@ +// 'z' is dynamically initialized global from different TU. +extern int z; +int __attribute__((noinline)) initY() { + return z + 1; +} +int y = initY(); diff --git a/lib/asan/lit_tests/Helpers/initialization-nobug-extra.cc b/lib/asan/lit_tests/Helpers/initialization-nobug-extra.cc new file mode 100644 index 000000000000..490b3339054a --- /dev/null +++ b/lib/asan/lit_tests/Helpers/initialization-nobug-extra.cc @@ -0,0 +1,9 @@ +// Linker initialized: +int getAB(); +static int ab = getAB(); +// Function local statics: +int countCalls(); +static int one = countCalls(); +// Constexpr: +int getCoolestInteger(); +static int coolest_integer = getCoolestInteger(); diff --git a/lib/asan/lit_tests/Helpers/lit.local.cfg b/lib/asan/lit_tests/Helpers/lit.local.cfg new file mode 100644 index 000000000000..2fc4d99456b0 --- /dev/null +++ b/lib/asan/lit_tests/Helpers/lit.local.cfg @@ -0,0 +1,3 @@ +# Sources in this directory are helper files for tests which test functionality +# involving multiple translation units. +config.suffixes = [] diff --git a/lib/asan/lit_tests/Linux/clone_test.cc b/lib/asan/lit_tests/Linux/clone_test.cc new file mode 100644 index 000000000000..ca13b22425f2 --- /dev/null +++ b/lib/asan/lit_tests/Linux/clone_test.cc @@ -0,0 +1,48 @@ +// Regression test for: +// http://code.google.com/p/address-sanitizer/issues/detail?id=37 + +// RUN: %clangxx_asan -m64 -O0 %s -o %t && %t | FileCheck %s +// RUN: %clangxx_asan -m64 -O1 %s -o %t && %t | FileCheck %s +// RUN: %clangxx_asan -m64 -O2 %s -o %t && %t | FileCheck %s +// RUN: %clangxx_asan -m64 -O3 %s -o %t && %t | FileCheck %s +// RUN: %clangxx_asan -m32 -O0 %s -o %t && %t | FileCheck %s +// RUN: %clangxx_asan -m32 -O1 %s -o %t && %t | FileCheck %s +// RUN: %clangxx_asan -m32 -O2 %s -o %t && %t | FileCheck %s +// RUN: %clangxx_asan -m32 -O3 %s -o %t && %t | FileCheck %s + +#include <stdio.h> +#include <sched.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +int Child(void *arg) { + char x[32] = {0}; // Stack gets poisoned. + printf("Child: %p\n", x); + _exit(1); // NoReturn, stack will remain unpoisoned unless we do something. +} + +int main(int argc, char **argv) { + const int kStackSize = 1 << 20; + char child_stack[kStackSize + 1]; + char *sp = child_stack + kStackSize; // Stack grows down. + printf("Parent: %p\n", sp); + pid_t clone_pid = clone(Child, sp, CLONE_FILES | CLONE_VM, NULL, 0, 0, 0); + int status; + pid_t wait_result = waitpid(clone_pid, &status, __WCLONE); + if (wait_result < 0) { + perror("waitpid"); + return 0; + } + if (wait_result == clone_pid && WIFEXITED(status)) { + // Make sure the child stack was indeed unpoisoned. + for (int i = 0; i < kStackSize; i++) + child_stack[i] = i; + int ret = child_stack[argc - 1]; + printf("PASSED\n"); + // CHECK: PASSED + return ret; + } + return 0; +} diff --git a/lib/asan/lit_tests/Linux/initialization-bug-any-order.cc b/lib/asan/lit_tests/Linux/initialization-bug-any-order.cc new file mode 100644 index 000000000000..645fe1c85ed4 --- /dev/null +++ b/lib/asan/lit_tests/Linux/initialization-bug-any-order.cc @@ -0,0 +1,37 @@ +// Test to make sure basic initialization order errors are caught. +// Check that on Linux initialization order bugs are caught +// independently on order in which we list source files. + +// RUN: %clangxx_asan -m64 -O0 %s %p/../Helpers/initialization-bug-extra.cc\ +// RUN: -fsanitize=init-order -o %t && %t 2>&1 \ +// RUN: | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m64 -O0 %p/../Helpers/initialization-bug-extra.cc %s\ +// RUN: -fsanitize=init-order -o %t && %t 2>&1 \ +// RUN: | %symbolize | FileCheck %s + +// Do not test with optimization -- the error may be optimized away. + +#include <cstdio> + +// 'y' is a dynamically initialized global residing in a different TU. This +// dynamic initializer will read the value of 'y' before main starts. The +// result is undefined behavior, which should be caught by initialization order +// checking. +extern int y; +int __attribute__((noinline)) initX() { + return y + 1; + // CHECK: {{AddressSanitizer: initialization-order-fiasco}} + // CHECK: {{READ of size .* at 0x.* thread T0}} + // CHECK: {{#0 0x.* in .*initX.* .*initialization-bug-any-order.cc:}}[[@LINE-3]] + // CHECK: {{0x.* is located 0 bytes inside of global variable .*y.*}} +} + +// This initializer begins our initialization order problems. +static int x = initX(); + +int main() { + // ASan should have caused an exit before main runs. + printf("PASS\n"); + // CHECK-NOT: PASS + return 0; +} diff --git a/lib/asan/lit_tests/Linux/interception_failure_test.cc b/lib/asan/lit_tests/Linux/interception_failure_test.cc new file mode 100644 index 000000000000..dfad909f528c --- /dev/null +++ b/lib/asan/lit_tests/Linux/interception_failure_test.cc @@ -0,0 +1,26 @@ +// If user provides his own libc functions, ASan doesn't +// intercept these functions. + +// RUN: %clangxx_asan -m64 -O0 %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -m64 -O1 %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -m64 -O2 %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -m64 -O3 %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -m32 -O0 %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -m32 -O1 %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -m32 -O2 %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -m32 -O3 %s -o %t && %t 2>&1 | FileCheck %s +#include <stdlib.h> +#include <stdio.h> + +extern "C" long strtol(const char *nptr, char **endptr, int base) { + fprintf(stderr, "my_strtol_interceptor\n"); + return 0; +} + +int main() { + char *x = (char*)malloc(10 * sizeof(char)); + free(x); + return (int)strtol(x, 0, 10); + // CHECK: my_strtol_interceptor + // CHECK-NOT: heap-use-after-free +} diff --git a/lib/asan/lit_tests/Linux/interception_malloc_test.cc b/lib/asan/lit_tests/Linux/interception_malloc_test.cc new file mode 100644 index 000000000000..8f66788e9a82 --- /dev/null +++ b/lib/asan/lit_tests/Linux/interception_malloc_test.cc @@ -0,0 +1,27 @@ +// ASan interceptor can be accessed with __interceptor_ prefix. + +// RUN: %clangxx_asan -m64 -O0 %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -m64 -O1 %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -m64 -O2 %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -m64 -O3 %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -m32 -O0 %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -m32 -O1 %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -m32 -O2 %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -m32 -O3 %s -o %t && %t 2>&1 | FileCheck %s +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> + +extern "C" void *__interceptor_malloc(size_t size); +extern "C" void *malloc(size_t size) { + write(2, "malloc call\n", sizeof("malloc call\n") - 1); + return __interceptor_malloc(size); +} + +int main() { + char *x = (char*)malloc(10 * sizeof(char)); + free(x); + return (int)strtol(x, 0, 10); + // CHECK: malloc call + // CHECK: heap-use-after-free +} diff --git a/lib/asan/lit_tests/Linux/interception_test.cc b/lib/asan/lit_tests/Linux/interception_test.cc new file mode 100644 index 000000000000..94fb499f2f87 --- /dev/null +++ b/lib/asan/lit_tests/Linux/interception_test.cc @@ -0,0 +1,26 @@ +// ASan interceptor can be accessed with __interceptor_ prefix. + +// RUN: %clangxx_asan -m64 -O0 %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -m64 -O1 %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -m64 -O2 %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -m64 -O3 %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -m32 -O0 %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -m32 -O1 %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -m32 -O2 %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -m32 -O3 %s -o %t && %t 2>&1 | FileCheck %s +#include <stdlib.h> +#include <stdio.h> + +extern "C" long __interceptor_strtol(const char *nptr, char **endptr, int base); +extern "C" long strtol(const char *nptr, char **endptr, int base) { + fprintf(stderr, "my_strtol_interceptor\n"); + return __interceptor_strtol(nptr, endptr, base); +} + +int main() { + char *x = (char*)malloc(10 * sizeof(char)); + free(x); + return (int)strtol(x, 0, 10); + // CHECK: my_strtol_interceptor + // CHECK: heap-use-after-free +} diff --git a/lib/asan/lit_tests/Linux/lit.local.cfg b/lib/asan/lit_tests/Linux/lit.local.cfg new file mode 100644 index 000000000000..57271b8078a4 --- /dev/null +++ b/lib/asan/lit_tests/Linux/lit.local.cfg @@ -0,0 +1,9 @@ +def getRoot(config): + if not config.parent: + return config + return getRoot(config.parent) + +root = getRoot(config) + +if root.host_os not in ['Linux']: + config.unsupported = True diff --git a/lib/asan/lit_tests/Linux/malloc-in-qsort.cc b/lib/asan/lit_tests/Linux/malloc-in-qsort.cc new file mode 100644 index 000000000000..a3fa255b186d --- /dev/null +++ b/lib/asan/lit_tests/Linux/malloc-in-qsort.cc @@ -0,0 +1,50 @@ +// RUN: %clangxx_asan -O2 %s -o %t +// RUN: ASAN_OPTIONS=fast_unwind_on_malloc=1 %t 2>&1 | %symbolize | FileCheck %s --check-prefix=CHECK-FAST +// RUN: ASAN_OPTIONS=fast_unwind_on_malloc=0 %t 2>&1 | %symbolize | FileCheck %s --check-prefix=CHECK-SLOW + +// Test how well we unwind in presence of qsort in the stack +// (i.e. if we can unwind through a function compiled w/o frame pointers). +// https://code.google.com/p/address-sanitizer/issues/detail?id=137 +#include <stdlib.h> +#include <stdio.h> + +int *GlobalPtr; + +extern "C" { +int QsortCallback(const void *a, const void *b) { + char *x = (char*)a; + char *y = (char*)b; + printf("Calling QsortCallback\n"); + GlobalPtr = new int[10]; + return (int)*x - (int)*y; +} + +__attribute__((noinline)) +void MyQsort(char *a, size_t size) { + printf("Calling qsort\n"); + qsort(a, size, sizeof(char), QsortCallback); + printf("Done\n"); // Avoid tail call. +} +} // extern "C" + +int main() { + char a[2] = {1, 2}; + MyQsort(a, 2); + return GlobalPtr[10]; +} + +// Fast unwind: can not unwind through qsort. +// FIXME: this test does not properly work with slow unwind yet. + +// CHECK-FAST: ERROR: AddressSanitizer: heap-buffer-overflow +// CHECK-FAST: is located 0 bytes to the right +// CHECK-FAST: #0{{.*}}operator new +// CHECK-FAST-NEXT: #1{{.*}}QsortCallback +// CHECK-FAST-NOT: MyQsort +// +// CHECK-SLOW: ERROR: AddressSanitizer: heap-buffer-overflow +// CHECK-SLOW: is located 0 bytes to the right +// CHECK-SLOW: #0{{.*}}operator new +// CHECK-SLOW-NEXT: #1{{.*}}QsortCallback +// CHECK-SLOW: #{{.*}}MyQsort +// CHECK-SLOW-NEXT: #{{.*}}main diff --git a/lib/asan/lit_tests/Linux/overflow-in-qsort.cc b/lib/asan/lit_tests/Linux/overflow-in-qsort.cc new file mode 100644 index 000000000000..c298991a8348 --- /dev/null +++ b/lib/asan/lit_tests/Linux/overflow-in-qsort.cc @@ -0,0 +1,47 @@ +// RUN: %clangxx_asan -O2 %s -o %t +// RUN: ASAN_OPTIONS=fast_unwind_on_fatal=1 %t 2>&1 | %symbolize | FileCheck %s --check-prefix=CHECK-FAST +// RUN: ASAN_OPTIONS=fast_unwind_on_fatal=0 %t 2>&1 | %symbolize | FileCheck %s --check-prefix=CHECK-SLOW + +// Test how well we unwind in presence of qsort in the stack +// (i.e. if we can unwind through a function compiled w/o frame pointers). +// https://code.google.com/p/address-sanitizer/issues/detail?id=137 +#include <stdlib.h> +#include <stdio.h> + +int global_array[10]; +volatile int one = 1; + +extern "C" { +int QsortCallback(const void *a, const void *b) { + char *x = (char*)a; + char *y = (char*)b; + printf("Calling QsortCallback\n"); + global_array[one * 10] = 0; // BOOM + return (int)*x - (int)*y; +} + +__attribute__((noinline)) +void MyQsort(char *a, size_t size) { + printf("Calling qsort\n"); + qsort(a, size, sizeof(char), QsortCallback); + printf("Done\n"); // Avoid tail call. +} +} // extern "C" + +int main() { + char a[2] = {1, 2}; + MyQsort(a, 2); +} + +// Fast unwind: can not unwind through qsort. + +// CHECK-FAST: ERROR: AddressSanitizer: global-buffer-overflow +// CHECK-FAST: #0{{.*}} in QsortCallback +// CHECK-FAST-NOT: MyQsort +// CHECK-FAST: is located 0 bytes to the right of global variable 'global_array + +// CHECK-SLOW: ERROR: AddressSanitizer: global-buffer-overflow +// CHECK-SLOW: #0{{.*}} in QsortCallback +// CHECK-SLOW: #{{.*}} in MyQsort +// CHECK-SLOW: #{{.*}} in main +// CHECK-SLOW: is located 0 bytes to the right of global variable 'global_array diff --git a/lib/asan/lit_tests/Linux/rlimit_mmap_test.cc b/lib/asan/lit_tests/Linux/rlimit_mmap_test.cc new file mode 100644 index 000000000000..5026e24e424d --- /dev/null +++ b/lib/asan/lit_tests/Linux/rlimit_mmap_test.cc @@ -0,0 +1,16 @@ +// Check that we properly report mmap failure. +// RUN: %clangxx_asan %s -o %t && %t 2>&1 | FileCheck %s +#include <stdlib.h> +#include <assert.h> +#include <sys/time.h> +#include <sys/resource.h> + +static volatile void *x; + +int main(int argc, char **argv) { + struct rlimit mmap_resource_limit = { 0, 0 }; + assert(0 == setrlimit(RLIMIT_AS, &mmap_resource_limit)); + x = malloc(10000000); +// CHECK: AddressSanitizer is unable to mmap + return 0; +} diff --git a/lib/asan/lit_tests/Linux/swapcontext_test.cc b/lib/asan/lit_tests/Linux/swapcontext_test.cc new file mode 100644 index 000000000000..0404b4f602bd --- /dev/null +++ b/lib/asan/lit_tests/Linux/swapcontext_test.cc @@ -0,0 +1,66 @@ +// Check that ASan plays well with easy cases of makecontext/swapcontext. + +// RUN: %clangxx_asan -m64 -O0 %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -m64 -O1 %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -m64 -O2 %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -m64 -O3 %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -m32 -O0 %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -m32 -O1 %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -m32 -O2 %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -m32 -O3 %s -o %t && %t 2>&1 | FileCheck %s + +#include <stdio.h> +#include <ucontext.h> +#include <unistd.h> + +ucontext_t orig_context; +ucontext_t child_context; + +void Child(int mode) { + char x[32] = {0}; // Stack gets poisoned. + printf("Child: %p\n", x); + // (a) Do nothing, just return to parent function. + // (b) Jump into the original function. Stack remains poisoned unless we do + // something. + if (mode == 1) { + if (swapcontext(&child_context, &orig_context) < 0) { + perror("swapcontext"); + _exit(0); + } + } +} + +int Run(int arg, int mode) { + const int kStackSize = 1 << 20; + char child_stack[kStackSize + 1]; + printf("Child stack: %p\n", child_stack); + // Setup child context. + getcontext(&child_context); + child_context.uc_stack.ss_sp = child_stack; + child_context.uc_stack.ss_size = kStackSize / 2; + if (mode == 0) { + child_context.uc_link = &orig_context; + } + makecontext(&child_context, (void (*)())Child, 1, mode); + if (swapcontext(&orig_context, &child_context) < 0) { + perror("swapcontext"); + return 0; + } + // Touch childs's stack to make sure it's unpoisoned. + for (int i = 0; i < kStackSize; i++) { + child_stack[i] = i; + } + return child_stack[arg]; +} + +int main(int argc, char **argv) { + // CHECK: WARNING: ASan doesn't fully support makecontext/swapcontext + int ret = 0; + ret += Run(argc - 1, 0); + printf("Test1 passed\n"); + // CHECK: Test1 passed + ret += Run(argc - 1, 1); + printf("Test2 passed\n"); + // CHECK: Test2 passed + return ret; +} diff --git a/lib/asan/output_tests/dlclose-test-so.cc b/lib/asan/lit_tests/SharedLibs/dlclose-test-so.cc index 73e00507358a..73e00507358a 100644 --- a/lib/asan/output_tests/dlclose-test-so.cc +++ b/lib/asan/lit_tests/SharedLibs/dlclose-test-so.cc diff --git a/lib/asan/lit_tests/SharedLibs/lit.local.cfg b/lib/asan/lit_tests/SharedLibs/lit.local.cfg new file mode 100644 index 000000000000..b3677c17a0f2 --- /dev/null +++ b/lib/asan/lit_tests/SharedLibs/lit.local.cfg @@ -0,0 +1,4 @@ +# Sources in this directory are compiled as shared libraries and used by +# tests in parent directory. + +config.suffixes = [] diff --git a/lib/asan/output_tests/shared-lib-test-so.cc b/lib/asan/lit_tests/SharedLibs/shared-lib-test-so.cc index 686a24578082..686a24578082 100644 --- a/lib/asan/output_tests/shared-lib-test-so.cc +++ b/lib/asan/lit_tests/SharedLibs/shared-lib-test-so.cc diff --git a/lib/asan/lit_tests/Unit/lit.cfg b/lib/asan/lit_tests/Unit/lit.cfg new file mode 100644 index 000000000000..243eb7fbeec0 --- /dev/null +++ b/lib/asan/lit_tests/Unit/lit.cfg @@ -0,0 +1,27 @@ +# -*- Python -*- + +import os + +def get_required_attr(config, attr_name): + attr_value = getattr(config, attr_name, None) + if not attr_value: + lit.fatal("No attribute %r in test configuration! You may need to run " + "tests from your build directory or add this attribute " + "to lit.site.cfg " % attr_name) + return attr_value + +# Setup attributes common for all compiler-rt projects. +llvm_src_root = get_required_attr(config, 'llvm_src_root') +compiler_rt_lit_unit_cfg = os.path.join(llvm_src_root, "projects", + "compiler-rt", "lib", + "lit.common.unit.cfg") +lit.load_config(config, compiler_rt_lit_unit_cfg) + +# Setup config name. +config.name = 'AddressSanitizer-Unit' + +# Setup test source and exec root. For unit tests, we define +# it as build directory with ASan unit tests. +asan_binary_dir = get_required_attr(config, "asan_binary_dir") +config.test_exec_root = os.path.join(asan_binary_dir, "tests") +config.test_source_root = config.test_exec_root diff --git a/lib/asan/lit_tests/Unit/lit.site.cfg.in b/lib/asan/lit_tests/Unit/lit.site.cfg.in new file mode 100644 index 000000000000..401c3a8cc2eb --- /dev/null +++ b/lib/asan/lit_tests/Unit/lit.site.cfg.in @@ -0,0 +1,10 @@ +## Autogenerated by LLVM/Clang configuration. +# Do not edit! + +config.target_triple = "@TARGET_TRIPLE@" +config.llvm_src_root = "@LLVM_SOURCE_DIR@" +config.build_type = "@CMAKE_BUILD_TYPE@" +config.asan_binary_dir = "@ASAN_BINARY_DIR@" + +# Let the main config do the real work. +lit.load_config(config, "@ASAN_SOURCE_DIR@/lit_tests/Unit/lit.cfg") diff --git a/lib/asan/lit_tests/blacklist.cc b/lib/asan/lit_tests/blacklist.cc new file mode 100644 index 000000000000..6cfc1500c503 --- /dev/null +++ b/lib/asan/lit_tests/blacklist.cc @@ -0,0 +1,44 @@ +// Test the blacklist functionality of ASan + +// RUN: echo "fun:*brokenFunction*" > %tmp +// RUN: echo "global:*badGlobal*" >> %tmp +// RUN: echo "src:*blacklist-extra.cc" >> %tmp +// RUN: %clangxx_asan -fsanitize-blacklist=%tmp -m64 -O0 %s -o %t \ +// RUN: %p/Helpers/blacklist-extra.cc && %t 2>&1 +// RUN: %clangxx_asan -fsanitize-blacklist=%tmp -m64 -O1 %s -o %t \ +// RUN: %p/Helpers/blacklist-extra.cc && %t 2>&1 +// RUN: %clangxx_asan -fsanitize-blacklist=%tmp -m64 -O2 %s -o %t \ +// RUN: %p/Helpers/blacklist-extra.cc && %t 2>&1 +// RUN: %clangxx_asan -fsanitize-blacklist=%tmp -m64 -O3 %s -o %t \ +// RUN: %p/Helpers/blacklist-extra.cc && %t 2>&1 +// RUN: %clangxx_asan -fsanitize-blacklist=%tmp -m32 -O0 %s -o %t \ +// RUN: %p/Helpers/blacklist-extra.cc && %t 2>&1 +// RUN: %clangxx_asan -fsanitize-blacklist=%tmp -m32 -O1 %s -o %t \ +// RUN: %p/Helpers/blacklist-extra.cc && %t 2>&1 +// RUN: %clangxx_asan -fsanitize-blacklist=%tmp -m32 -O2 %s -o %t \ +// RUN: %p/Helpers/blacklist-extra.cc && %t 2>&1 +// RUN: %clangxx_asan -fsanitize-blacklist=%tmp -m32 -O3 %s -o %t \ +// RUN: %p/Helpers/blacklist-extra.cc && %t 2>&1 + +// badGlobal is accessed improperly, but we blacklisted it. +int badGlobal; +int readBadGlobal() { + return (&badGlobal)[1]; +} + +// A function which is broken, but excluded in the blacklist. +int brokenFunction(int argc) { + char x[10] = {0}; + return x[argc * 10]; // BOOM +} + +// This function is defined in Helpers/blacklist-extra.cc, a source file which +// is blacklisted by name +int externalBrokenFunction(int x); + +int main(int argc, char **argv) { + brokenFunction(argc); + int x = readBadGlobal(); + externalBrokenFunction(argc); + return 0; +} diff --git a/lib/asan/lit_tests/deep_stack_uaf.cc b/lib/asan/lit_tests/deep_stack_uaf.cc new file mode 100644 index 000000000000..7b32798fefcc --- /dev/null +++ b/lib/asan/lit_tests/deep_stack_uaf.cc @@ -0,0 +1,36 @@ +// Check that we can store lots of stack frames if asked to. + +// RUN: %clangxx_asan -m64 -O0 %s -o %t 2>&1 +// RUN: ASAN_OPTIONS=malloc_context_size=120:redzone=512 %t 2>&1 | \ +// RUN: %symbolize | FileCheck %s + +// RUN: %clangxx_asan -m32 -O0 %s -o %t 2>&1 +// RUN: ASAN_OPTIONS=malloc_context_size=120:redzone=512 %t 2>&1 | \ +// RUN: %symbolize | FileCheck %s +#include <stdlib.h> +#include <stdio.h> + +template <int depth> +struct DeepFree { + static void free(char *x) { + DeepFree<depth - 1>::free(x); + } +}; + +template<> +struct DeepFree<0> { + static void free(char *x) { + ::free(x); + } +}; + +int main() { + char *x = (char*)malloc(10); + // deep_free(x); + DeepFree<200>::free(x); + return x[5]; + // CHECK: {{.*ERROR: AddressSanitizer: heap-use-after-free on address}} + // CHECK: DeepFree<36> + // CHECK: DeepFree<98> + // CHECK: DeepFree<115> +} diff --git a/lib/asan/lit_tests/deep_tail_call.cc b/lib/asan/lit_tests/deep_tail_call.cc new file mode 100644 index 000000000000..6aa15e81f6ec --- /dev/null +++ b/lib/asan/lit_tests/deep_tail_call.cc @@ -0,0 +1,24 @@ +// RUN: %clangxx_asan -m64 -O0 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m64 -O1 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m64 -O2 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m64 -O3 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m32 -O0 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m32 -O1 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m32 -O2 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m32 -O3 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s + +// CHECK: AddressSanitizer: global-buffer-overflow +int global[10]; +// CHECK: {{#0.*call4}} +void __attribute__((noinline)) call4(int i) { global[i+10]++; } +// CHECK: {{#1.*call3}} +void __attribute__((noinline)) call3(int i) { call4(i); } +// CHECK: {{#2.*call2}} +void __attribute__((noinline)) call2(int i) { call3(i); } +// CHECK: {{#3.*call1}} +void __attribute__((noinline)) call1(int i) { call2(i); } +// CHECK: {{#4.*main}} +int main(int argc, char **argv) { + call1(argc); + return global[0]; +} diff --git a/lib/asan/lit_tests/deep_thread_stack.cc b/lib/asan/lit_tests/deep_thread_stack.cc new file mode 100644 index 000000000000..781508d61616 --- /dev/null +++ b/lib/asan/lit_tests/deep_thread_stack.cc @@ -0,0 +1,61 @@ +// RUN: %clangxx_asan -m64 -O0 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m64 -O1 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m64 -O2 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m64 -O3 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m32 -O0 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m32 -O1 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m32 -O2 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m32 -O3 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s + +#include <pthread.h> + +int *x; + +void *AllocThread(void *arg) { + x = new int; + *x = 42; + return NULL; +} + +void *FreeThread(void *arg) { + delete x; + return NULL; +} + +void *AccessThread(void *arg) { + *x = 43; // BOOM + return NULL; +} + +typedef void* (*callback_type)(void* arg); + +void *RunnerThread(void *function) { + pthread_t thread; + pthread_create(&thread, NULL, (callback_type)function, NULL); + pthread_join(thread, NULL); + return NULL; +} + +void RunThread(callback_type function) { + pthread_t runner; + pthread_create(&runner, NULL, RunnerThread, (void*)function); + pthread_join(runner, NULL); +} + +int main(int argc, char *argv[]) { + RunThread(AllocThread); + RunThread(FreeThread); + RunThread(AccessThread); + return (x != 0); +} + +// CHECK: AddressSanitizer: heap-use-after-free +// CHECK: WRITE of size 4 at 0x{{.*}} thread T[[ACCESS_THREAD:[0-9]+]] +// CHECK: freed by thread T[[FREE_THREAD:[0-9]+]] here: +// CHECK: previously allocated by thread T[[ALLOC_THREAD:[0-9]+]] here: +// CHECK: Thread T[[ACCESS_THREAD]] created by T[[ACCESS_RUNNER:[0-9]+]] here: +// CHECK: Thread T[[ACCESS_RUNNER]] created by T0 here: +// CHECK: Thread T[[FREE_THREAD]] created by T[[FREE_RUNNER:[0-9]+]] here: +// CHECK: Thread T[[FREE_RUNNER]] created by T0 here: +// CHECK: Thread T[[ALLOC_THREAD]] created by T[[ALLOC_RUNNER:[0-9]+]] here: +// CHECK: Thread T[[ALLOC_RUNNER]] created by T0 here: diff --git a/lib/asan/output_tests/default_options.cc b/lib/asan/lit_tests/default_options.cc index d6c70291e6ff..950a7d879194 100644 --- a/lib/asan/output_tests/default_options.cc +++ b/lib/asan/lit_tests/default_options.cc @@ -1,12 +1,15 @@ +// RUN: %clangxx_asan -O2 %s -o %t +// RUN: %t 2>&1 | FileCheck %s + const char *kAsanDefaultOptions="verbosity=1 foo=bar"; extern "C" __attribute__((no_address_safety_analysis)) const char *__asan_default_options() { + // CHECK: Using the defaults from __asan_default_options: {{.*}} foo=bar return kAsanDefaultOptions; } int main() { - // Check-Common: foo=bar return 0; } diff --git a/lib/asan/output_tests/dlclose-test.cc b/lib/asan/lit_tests/dlclose-test.cc index 16126eb9042f..229f508294bf 100644 --- a/lib/asan/output_tests/dlclose-test.cc +++ b/lib/asan/lit_tests/dlclose-test.cc @@ -1,14 +1,3 @@ -//===----------- dlclose-test.cc --------------------------------*- C++ -*-===// -// -// 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. -// // Regression test for // http://code.google.com/p/address-sanitizer/issues/detail?id=19 // Bug description: @@ -19,7 +8,32 @@ // 5. application starts using this mmaped memory, but asan still thinks there // are globals. // 6. BOOM -//===----------------------------------------------------------------------===// + +// RUN: %clangxx_asan -m64 -O0 %p/SharedLibs/dlclose-test-so.cc \ +// RUN: -fPIC -shared -o %t-so.so +// RUN: %clangxx_asan -m64 -O0 %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -m64 -O1 %p/SharedLibs/dlclose-test-so.cc \ +// RUN: -fPIC -shared -o %t-so.so +// RUN: %clangxx_asan -m64 -O1 %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -m64 -O2 %p/SharedLibs/dlclose-test-so.cc \ +// RUN: -fPIC -shared -o %t-so.so +// RUN: %clangxx_asan -m64 -O2 %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -m64 -O3 %p/SharedLibs/dlclose-test-so.cc \ +// RUN: -fPIC -shared -o %t-so.so +// RUN: %clangxx_asan -m64 -O3 %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -m32 -O0 %p/SharedLibs/dlclose-test-so.cc \ +// RUN: -fPIC -shared -o %t-so.so +// RUN: %clangxx_asan -m32 -O0 %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -m32 -O1 %p/SharedLibs/dlclose-test-so.cc \ +// RUN: -fPIC -shared -o %t-so.so +// RUN: %clangxx_asan -m32 -O1 %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -m32 -O2 %p/SharedLibs/dlclose-test-so.cc \ +// RUN: -fPIC -shared -o %t-so.so +// RUN: %clangxx_asan -m32 -O2 %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -m32 -O3 %p/SharedLibs/dlclose-test-so.cc \ +// RUN: -fPIC -shared -o %t-so.so +// RUN: %clangxx_asan -m32 -O3 %s -o %t && %t 2>&1 | FileCheck %s + #include <assert.h> #include <dlfcn.h> #include <stdio.h> @@ -69,6 +83,6 @@ int main(int argc, char *argv[]) { } addr[1] = 2; // BOOM (if the bug is not fixed). printf("PASS\n"); - // Check-Common: PASS + // CHECK: PASS return 0; } diff --git a/lib/asan/lit_tests/force_inline_opt0.cc b/lib/asan/lit_tests/force_inline_opt0.cc new file mode 100644 index 000000000000..955ce38156fb --- /dev/null +++ b/lib/asan/lit_tests/force_inline_opt0.cc @@ -0,0 +1,14 @@ +// This test checks that we are no instrumenting a memory access twice +// (before and after inlining) +// RUN: %clangxx_asan -m64 -O1 %s -o %t && %t +// RUN: %clangxx_asan -m64 -O0 %s -o %t && %t +__attribute__((always_inline)) +void foo(int *x) { + *x = 0; +} + +int main() { + int x; + foo(&x); + return x; +} diff --git a/lib/asan/lit_tests/global-overflow.cc b/lib/asan/lit_tests/global-overflow.cc new file mode 100644 index 000000000000..6a2f12e106fe --- /dev/null +++ b/lib/asan/lit_tests/global-overflow.cc @@ -0,0 +1,25 @@ +// RUN: %clangxx_asan -m64 -O0 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m64 -O1 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m64 -O2 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m64 -O3 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m32 -O0 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m32 -O1 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m32 -O2 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m32 -O3 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s + +#include <string.h> +int main(int argc, char **argv) { + static char XXX[10]; + static char YYY[10]; + static char ZZZ[10]; + memset(XXX, 0, 10); + memset(YYY, 0, 10); + memset(ZZZ, 0, 10); + int res = YYY[argc * 10]; // BOOOM + // CHECK: {{READ of size 1 at 0x.* thread T0}} + // CHECK: {{ #0 0x.* in _?main .*global-overflow.cc:}}[[@LINE-2]] + // CHECK: {{0x.* is located 0 bytes to the right of global variable}} + // CHECK: {{.*YYY.* of size 10}} + res += XXX[argc] + ZZZ[argc]; + return res; +} diff --git a/lib/asan/lit_tests/heap-overflow.cc b/lib/asan/lit_tests/heap-overflow.cc new file mode 100644 index 000000000000..2648ec7e5f1f --- /dev/null +++ b/lib/asan/lit_tests/heap-overflow.cc @@ -0,0 +1,38 @@ +// RUN: %clangxx_asan -m64 -O0 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out +// RUN: %clangxx_asan -m64 -O1 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out +// RUN: %clangxx_asan -m64 -O2 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out +// RUN: %clangxx_asan -m64 -O3 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out +// RUN: %clangxx_asan -m32 -O0 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out +// RUN: %clangxx_asan -m32 -O1 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out +// RUN: %clangxx_asan -m32 -O2 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out +// RUN: %clangxx_asan -m32 -O3 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out + +#include <stdlib.h> +#include <string.h> +int main(int argc, char **argv) { + char *x = (char*)malloc(10 * sizeof(char)); + memset(x, 0, 10); + int res = x[argc * 10]; // BOOOM + // CHECK: {{READ of size 1 at 0x.* thread T0}} + // CHECK: {{ #0 0x.* in _?main .*heap-overflow.cc:}}[[@LINE-2]] + // CHECK: {{0x.* is located 0 bytes to the right of 10-byte region}} + // CHECK: {{allocated by thread T0 here:}} + + // CHECK-Linux: {{ #0 0x.* in .*malloc}} + // CHECK-Linux: {{ #1 0x.* in main .*heap-overflow.cc:21}} + + // CHECK-Darwin: {{ #0 0x.* in .*mz_malloc.*}} + // CHECK-Darwin: {{ #1 0x.* in malloc_zone_malloc.*}} + // CHECK-Darwin: {{ #2 0x.* in malloc.*}} + // CHECK-Darwin: {{ #3 0x.* in _?main .*heap-overflow.cc:21}} + free(x); + return res; +} diff --git a/lib/asan/lit_tests/initialization-blacklist.cc b/lib/asan/lit_tests/initialization-blacklist.cc new file mode 100644 index 000000000000..f8df24c68ea6 --- /dev/null +++ b/lib/asan/lit_tests/initialization-blacklist.cc @@ -0,0 +1,32 @@ +// Test for blacklist functionality of initialization-order checker. + +// RUN: %clangxx_asan -m64 -O0 %s %p/Helpers/initialization-blacklist-extra.cc\ +// RUN: -fsanitize-blacklist=%p/Helpers/initialization-blacklist.txt \ +// RUN: -fsanitize=init-order -o %t && %t 2>&1 +// RUN: %clangxx_asan -m64 -O1 %s %p/Helpers/initialization-blacklist-extra.cc\ +// RUN: -fsanitize-blacklist=%p/Helpers/initialization-blacklist.txt \ +// RUN: -fsanitize=init-order -o %t && %t 2>&1 +// RUN: %clangxx_asan -m64 -O2 %s %p/Helpers/initialization-blacklist-extra.cc\ +// RUN: -fsanitize-blacklist=%p/Helpers/initialization-blacklist.txt \ +// RUN: -fsanitize=init-order -o %t && %t 2>&1 +// RUN: %clangxx_asan -m32 -O0 %s %p/Helpers/initialization-blacklist-extra.cc\ +// RUN: -fsanitize-blacklist=%p/Helpers/initialization-blacklist.txt \ +// RUN: -fsanitize=init-order -o %t && %t 2>&1 +// RUN: %clangxx_asan -m32 -O1 %s %p/Helpers/initialization-blacklist-extra.cc\ +// RUN: -fsanitize-blacklist=%p/Helpers/initialization-blacklist.txt \ +// RUN: -fsanitize=init-order -o %t && %t 2>&1 +// RUN: %clangxx_asan -m32 -O2 %s %p/Helpers/initialization-blacklist-extra.cc\ +// RUN: -fsanitize-blacklist=%p/Helpers/initialization-blacklist.txt \ +// RUN: -fsanitize=init-order -o %t && %t 2>&1 + +// Function is defined in another TU. +int readBadGlobal(); +int x = readBadGlobal(); // init-order bug. + +// Function is defined in another TU. +int accessBadObject(); +int y = accessBadObject(); // init-order bug. + +int main(int argc, char **argv) { + return argc + x + y - 1; +} diff --git a/lib/asan/lit_tests/initialization-bug.cc b/lib/asan/lit_tests/initialization-bug.cc new file mode 100644 index 000000000000..8f4e33ef5a35 --- /dev/null +++ b/lib/asan/lit_tests/initialization-bug.cc @@ -0,0 +1,46 @@ +// Test to make sure basic initialization order errors are caught. + +// RUN: %clangxx_asan -m64 -O0 %s %p/Helpers/initialization-bug-extra2.cc\ +// RUN: -fsanitize=init-order -o %t && %t 2>&1 \ +// RUN: | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m32 -O0 %s %p/Helpers/initialization-bug-extra2.cc\ +// RUN: -fsanitize=init-order -o %t && %t 2>&1 \ +// RUN: | %symbolize | FileCheck %s + +// Do not test with optimization -- the error may be optimized away. + +#include <cstdio> + +// The structure of the test is: +// "x", "y", "z" are dynamically initialized globals. +// Value of "x" depends on "y", value of "y" depends on "z". +// "x" and "z" are defined in this TU, "y" is defined in another one. +// Thus we shoud stably report initialization order fiasco independently of +// the translation unit order. + +int initZ() { + return 5; +} +int z = initZ(); + +// 'y' is a dynamically initialized global residing in a different TU. This +// dynamic initializer will read the value of 'y' before main starts. The +// result is undefined behavior, which should be caught by initialization order +// checking. +extern int y; +int __attribute__((noinline)) initX() { + return y + 1; + // CHECK: {{AddressSanitizer: initialization-order-fiasco}} + // CHECK: {{READ of size .* at 0x.* thread T0}} + // CHECK: {{0x.* is located 0 bytes inside of global variable .*(y|z).*}} +} + +// This initializer begins our initialization order problems. +static int x = initX(); + +int main() { + // ASan should have caused an exit before main runs. + printf("PASS\n"); + // CHECK-NOT: PASS + return 0; +} diff --git a/lib/asan/lit_tests/initialization-nobug.cc b/lib/asan/lit_tests/initialization-nobug.cc new file mode 100644 index 000000000000..1b8961606811 --- /dev/null +++ b/lib/asan/lit_tests/initialization-nobug.cc @@ -0,0 +1,67 @@ +// A collection of various initializers which shouldn't trip up initialization +// order checking. If successful, this will just return 0. + +// RUN: %clangxx_asan -m64 -O0 %s %p/Helpers/initialization-nobug-extra.cc\ +// RUN: --std=c++11 -fsanitize=init-order -o %t && %t 2>&1 +// RUN: %clangxx_asan -m64 -O1 %s %p/Helpers/initialization-nobug-extra.cc\ +// RUN: --std=c++11 -fsanitize=init-order -o %t && %t 2>&1 +// RUN: %clangxx_asan -m64 -O2 %s %p/Helpers/initialization-nobug-extra.cc\ +// RUN: --std=c++11 -fsanitize=init-order -o %t && %t 2>&1 +// RUN: %clangxx_asan -m64 -O3 %s %p/Helpers/initialization-nobug-extra.cc\ +// RUN: --std=c++11 -fsanitize=init-order -o %t && %t 2>&1 +// RUN: %clangxx_asan -m32 -O0 %s %p/Helpers/initialization-nobug-extra.cc\ +// RUN: --std=c++11 -fsanitize=init-order -o %t && %t 2>&1 +// RUN: %clangxx_asan -m32 -O0 %s %p/Helpers/initialization-nobug-extra.cc\ +// RUN: --std=c++11 -fsanitize=init-order -o %t && %t 2>&1 +// RUN: %clangxx_asan -m32 -O1 %s %p/Helpers/initialization-nobug-extra.cc\ +// RUN: --std=c++11 -fsanitize=init-order -o %t && %t 2>&1 +// RUN: %clangxx_asan -m32 -O2 %s %p/Helpers/initialization-nobug-extra.cc\ +// RUN: --std=c++11 -fsanitize=init-order -o %t && %t 2>&1 +// RUN: %clangxx_asan -m32 -O3 %s %p/Helpers/initialization-nobug-extra.cc\ +// RUN: --std=c++11 -fsanitize=init-order -o %t && %t 2>&1 + +// Simple access: +// Make sure that accessing a global in the same TU is safe + +bool condition = true; +int initializeSameTU() { + return condition ? 0x2a : 052; +} +int sameTU = initializeSameTU(); + +// Linker initialized: +// Check that access to linker initialized globals originating from a different +// TU's initializer is safe. + +int A = (1 << 1) + (1 << 3) + (1 << 5), B; +int getAB() { + return A * B; +} + +// Function local statics: +// Check that access to function local statics originating from a different +// TU's initializer is safe. + +int countCalls() { + static int calls; + return ++calls; +} + +// Constexpr: +// We need to check that a global variable initialized with a constexpr +// constructor can be accessed during dynamic initialization (as a constexpr +// constructor implies that it was initialized during constant initialization, +// not dynamic initialization). + +class Integer { + private: + int value; + + public: + constexpr Integer(int x = 0) : value(x) {} + int getValue() {return value;} +}; +Integer coolestInteger(42); +int getCoolestInteger() { return coolestInteger.getValue(); } + +int main() { return 0; } diff --git a/lib/asan/lit_tests/interface_symbols.c b/lib/asan/lit_tests/interface_symbols.c new file mode 100644 index 000000000000..f3167f562922 --- /dev/null +++ b/lib/asan/lit_tests/interface_symbols.c @@ -0,0 +1,28 @@ +// Check the presense of interface symbols in compiled file. + +// RUN: %clang -fsanitize=address -dead_strip -O2 %s -o %t.exe +// RUN: nm %t.exe | grep " T " | sed "s/.* T //" \ +// RUN: | grep "__asan_" | sed "s/___asan_/__asan_/" \ +// RUN: | grep -v "__asan_malloc_hook" \ +// RUN: | grep -v "__asan_free_hook" \ +// RUN: | grep -v "__asan_symbolize" \ +// RUN: | grep -v "__asan_default_options" \ +// RUN: | grep -v "__asan_on_error" > %t.symbols +// RUN: cat %p/../../../include/sanitizer/asan_interface.h \ +// RUN: | sed "s/\/\/.*//" | sed "s/typedef.*//" \ +// RUN: | grep -v "OPTIONAL" \ +// RUN: | grep "__asan_.*(" | sed "s/.* __asan_/__asan_/;s/(.*//" \ +// RUN: > %t.interface +// RUN: echo __asan_report_load1 >> %t.interface +// RUN: echo __asan_report_load2 >> %t.interface +// RUN: echo __asan_report_load4 >> %t.interface +// RUN: echo __asan_report_load8 >> %t.interface +// RUN: echo __asan_report_load16 >> %t.interface +// RUN: echo __asan_report_store1 >> %t.interface +// RUN: echo __asan_report_store2 >> %t.interface +// RUN: echo __asan_report_store4 >> %t.interface +// RUN: echo __asan_report_store8 >> %t.interface +// RUN: echo __asan_report_store16 >> %t.interface +// RUN: cat %t.interface | sort -u | diff %t.symbols - + +int main() { return 0; } diff --git a/lib/asan/lit_tests/large_func_test.cc b/lib/asan/lit_tests/large_func_test.cc new file mode 100644 index 000000000000..a74828811f74 --- /dev/null +++ b/lib/asan/lit_tests/large_func_test.cc @@ -0,0 +1,62 @@ +// RUN: %clangxx_asan -m64 -O0 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out +// RUN: %clangxx_asan -m64 -O1 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out +// RUN: %clangxx_asan -m64 -O2 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out +// RUN: %clangxx_asan -m64 -O3 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out +// RUN: %clangxx_asan -m32 -O0 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out +// RUN: %clangxx_asan -m32 -O1 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out +// RUN: %clangxx_asan -m32 -O2 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out +// RUN: %clangxx_asan -m32 -O3 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out + +#include <stdlib.h> +__attribute__((noinline)) +static void LargeFunction(int *x, int zero) { + x[0]++; + x[1]++; + x[2]++; + x[3]++; + x[4]++; + x[5]++; + x[6]++; + x[7]++; + x[8]++; + x[9]++; + + // CHECK: {{.*ERROR: AddressSanitizer: heap-buffer-overflow on address}} + // CHECK: {{0x.* at pc 0x.* bp 0x.* sp 0x.*}} + // CHECK: {{READ of size 4 at 0x.* thread T0}} + x[zero + 111]++; // we should report this exact line + // atos incorrectly extracts the symbol name for the static functions on + // Darwin. + // CHECK-Linux: {{#0 0x.* in LargeFunction.*large_func_test.cc:}}[[@LINE-3]] + // CHECK-Darwin: {{#0 0x.* in .*LargeFunction.*large_func_test.cc}}:[[@LINE-4]] + + x[10]++; + x[11]++; + x[12]++; + x[13]++; + x[14]++; + x[15]++; + x[16]++; + x[17]++; + x[18]++; + x[19]++; +} + +int main(int argc, char **argv) { + int *x = new int[100]; + LargeFunction(x, argc - 1); + // CHECK: {{ #1 0x.* in _?main .*large_func_test.cc:}}[[@LINE-1]] + // CHECK: {{0x.* is located 44 bytes to the right of 400-byte region}} + // CHECK: {{allocated by thread T0 here:}} + // CHECK: {{ #0 0x.* in operator new.*}} + // CHECK: {{ #1 0x.* in _?main .*large_func_test.cc:}}[[@LINE-6]] + delete x; +} diff --git a/lib/asan/lit_tests/lit.cfg b/lib/asan/lit_tests/lit.cfg new file mode 100644 index 000000000000..7875281b1f2f --- /dev/null +++ b/lib/asan/lit_tests/lit.cfg @@ -0,0 +1,97 @@ +# -*- Python -*- + +import os + +# Setup config name. +config.name = 'AddressSanitizer' + +# Setup source root. +config.test_source_root = os.path.dirname(__file__) + +def DisplayNoConfigMessage(): + lit.fatal("No site specific configuration available! " + + "Try running your test from the build tree or running " + + "make check-asan") + +# Figure out LLVM source root. +llvm_src_root = getattr(config, 'llvm_src_root', None) +if llvm_src_root is None: + # We probably haven't loaded the site-specific configuration: the user + # is likely trying to run a test file directly, and the site configuration + # wasn't created by the build system. + asan_site_cfg = lit.params.get('asan_site_config', None) + if (asan_site_cfg) and (os.path.exists(asan_site_cfg)): + lit.load_config(config, asan_site_cfg) + raise SystemExit + + # Try to guess the location of site-specific configuration using llvm-config + # util that can point where the build tree is. + llvm_config = lit.util.which("llvm-config", config.environment["PATH"]) + if not llvm_config: + DisplayNoConfigMessage() + + # Validate that llvm-config points to the same source tree. + llvm_src_root = lit.util.capture(["llvm-config", "--src-root"]).strip() + asan_test_src_root = os.path.join(llvm_src_root, "projects", "compiler-rt", + "lib", "asan", "lit_tests") + if (os.path.realpath(asan_test_src_root) != + os.path.realpath(config.test_source_root)): + DisplayNoConfigMessage() + + # Find out the presumed location of generated site config. + llvm_obj_root = lit.util.capture(["llvm-config", "--obj-root"]).strip() + asan_site_cfg = os.path.join(llvm_obj_root, "projects", "compiler-rt", + "lib", "asan", "lit_tests", "lit.site.cfg") + if (not asan_site_cfg) or (not os.path.exists(asan_site_cfg)): + DisplayNoConfigMessage() + + lit.load_config(config, asan_site_cfg) + raise SystemExit + +# Setup attributes common for all compiler-rt projects. +compiler_rt_lit_cfg = os.path.join(llvm_src_root, "projects", "compiler-rt", + "lib", "lit.common.cfg") +if (not compiler_rt_lit_cfg) or (not os.path.exists(compiler_rt_lit_cfg)): + lit.fatal("Can't find common compiler-rt lit config at: %r" + % compiler_rt_lit_cfg) +lit.load_config(config, compiler_rt_lit_cfg) + +# Setup default compiler flags used with -fsanitize=address option. +# FIXME: Review the set of required flags and check if it can be reduced. +clang_asan_cxxflags = ("-ccc-cxx " + + "-fsanitize=address " + + "-mno-omit-leaf-frame-pointer " + + "-fno-omit-frame-pointer " + + "-fno-optimize-sibling-calls " + + "-g") +config.substitutions.append( ("%clangxx_asan ", (" " + config.clang + " " + + clang_asan_cxxflags + " ")) ) + +# Setup path to external LLVM symbolizer to run AddressSanitizer output tests. +llvm_tools_dir = getattr(config, 'llvm_tools_dir', None) +if llvm_tools_dir: + config.environment['LLVM_SYMBOLIZER_PATH'] = os.path.join( + llvm_tools_dir, "llvm-symbolizer") + +# Setup path to symbolizer script. +# FIXME: Instead we should copy this script to the build tree and point +# at it there. +asan_source_dir = os.path.join(config.test_source_root, "..") +symbolizer = os.path.join(asan_source_dir, + 'scripts', 'asan_symbolize.py') +if not os.path.exists(symbolizer): + lit.fatal("Can't find symbolizer script on path %r" % symbolizer) +# Define %symbolize substitution that filters output through +# symbolizer and c++filt (for demangling). +config.substitutions.append( ("%symbolize ", (" " + symbolizer + + " | c++filt " ))) + +# Define CHECK-%os to check for OS-dependent output. +config.substitutions.append( ('CHECK-%os', ("CHECK-" + config.host_os))) + +# Default test suffixes. +config.suffixes = ['.c', '.cc', '.cpp'] + +# AddressSanitizer tests are currently supported on Linux and Darwin only. +if config.host_os not in ['Linux', 'Darwin']: + config.unsupported = True diff --git a/lib/asan/lit_tests/lit.site.cfg.in b/lib/asan/lit_tests/lit.site.cfg.in new file mode 100644 index 000000000000..cf439309c6ad --- /dev/null +++ b/lib/asan/lit_tests/lit.site.cfg.in @@ -0,0 +1,20 @@ +## Autogenerated by LLVM/Clang configuration. +# Do not edit! + +config.target_triple = "@TARGET_TRIPLE@" +config.host_os = "@HOST_OS@" +config.llvm_src_root = "@LLVM_SOURCE_DIR@" +config.llvm_obj_root = "@LLVM_BINARY_DIR@" +config.llvm_tools_dir = "@LLVM_TOOLS_DIR@" +config.clang = "@LLVM_BINARY_DIR@/bin/clang" + +# LLVM tools dir can be passed in lit parameters, so try to +# apply substitution. +try: + config.llvm_tools_dir = config.llvm_tools_dir % lit.params +except KeyError,e: + key, = e.args + lit.fatal("unable to find %r parameter, use '--param=%s=VALUE'" % (key, key)) + +# Let the main config do the real work. +lit.load_config(config, "@ASAN_SOURCE_DIR@/lit_tests/lit.cfg") diff --git a/lib/asan/lit_tests/log-path_test.cc b/lib/asan/lit_tests/log-path_test.cc new file mode 100644 index 000000000000..1072670fbff4 --- /dev/null +++ b/lib/asan/lit_tests/log-path_test.cc @@ -0,0 +1,39 @@ +// RUN: %clangxx_asan %s -o %t + +// Regular run. +// RUN: not %t 2> %t.out +// RUN: FileCheck %s --check-prefix=CHECK-ERROR < %t.out + +// Good log_path. +// RUN: rm -f %t.log.* +// RUN: ASAN_OPTIONS=log_path=%t.log not %t 2> %t.out +// RUN: FileCheck %s --check-prefix=CHECK-ERROR < %t.log.* + +// Invalid log_path. +// RUN: ASAN_OPTIONS=log_path=/INVALID not %t 2> %t.out +// RUN: FileCheck %s --check-prefix=CHECK-INVALID < %t.out + +// Too long log_path. +// RUN: ASAN_OPTIONS=log_path=`for((i=0;i<10000;i++)); do echo -n $i; done` \ +// RUN: not %t 2> %t.out +// RUN: FileCheck %s --check-prefix=CHECK-LONG < %t.out + +// Run w/o errors should not produce any log. +// RUN: rm -f %t.log.* +// RUN: ASAN_OPTIONS=log_path=%t.log %t ARG ARG ARG +// RUN: not cat %t.log.* + + +#include <stdlib.h> +#include <string.h> +int main(int argc, char **argv) { + if (argc > 2) return 0; + char *x = (char*)malloc(10); + memset(x, 0, 10); + int res = x[argc * 10]; // BOOOM + free(x); + return res; +} +// CHECK-ERROR: ERROR: AddressSanitizer +// CHECK-INVALID: ERROR: Can't open file: /INVALID +// CHECK-LONG: ERROR: Path is too long: 01234 diff --git a/lib/asan/lit_tests/log_path_fork_test.cc b/lib/asan/lit_tests/log_path_fork_test.cc new file mode 100644 index 000000000000..c6c1b49e994d --- /dev/null +++ b/lib/asan/lit_tests/log_path_fork_test.cc @@ -0,0 +1,22 @@ +// RUN: %clangxx_asan %s -o %t +// RUN: rm -f %t.log.* +// Set verbosity to 1 so that the log files are opened prior to fork(). +// RUN: ASAN_OPTIONS="log_path=%t.log verbosity=1" not %t 2> %t.out +// RUN: for f in %t.log.* ; do FileCheck %s < $f; done +// RUN: [ `ls %t.log.* | wc -l` == 2 ] + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +int main(int argc, char **argv) { + void *x = malloc(10); + free(x); + if (fork() == -1) return 1; + // There are two processes at this point, thus there should be two distinct + // error logs. + free(x); + return 0; +} + +// CHECK: ERROR: AddressSanitizer diff --git a/lib/asan/lit_tests/malloc_delete_mismatch.cc b/lib/asan/lit_tests/malloc_delete_mismatch.cc new file mode 100644 index 000000000000..f34b33a38fb3 --- /dev/null +++ b/lib/asan/lit_tests/malloc_delete_mismatch.cc @@ -0,0 +1,26 @@ +// Check that we detect malloc/delete mismatch only if the approptiate flag +// is set. + +// RUN: %clangxx_asan -g %s -o %t 2>&1 +// RUN: ASAN_OPTIONS=alloc_dealloc_mismatch=1 %t 2>&1 | \ +// RUN: %symbolize | FileCheck %s + +// No error here. +// RUN: ASAN_OPTIONS=alloc_dealloc_mismatch=0 %t +#include <stdlib.h> + +static volatile char *x; + +int main() { + x = (char*)malloc(10); + x[0] = 0; + delete x; +} +// CHECK: ERROR: AddressSanitizer: alloc-dealloc-mismatch (malloc vs operator delete) on 0x +// CHECK-NEXT: #0{{.*}}operator delete +// CHECK: #{{.*}}main +// CHECK: is located 0 bytes inside of 10-byte region +// CHECK-NEXT: allocated by thread T0 here: +// CHECK-NEXT: #0{{.*}}malloc +// CHECK: #{{.*}}main +// CHECK: HINT: {{.*}} you may set ASAN_OPTIONS=alloc_dealloc_mismatch=0 diff --git a/lib/asan/lit_tests/malloc_hook.cc b/lib/asan/lit_tests/malloc_hook.cc new file mode 100644 index 000000000000..6435d105ee26 --- /dev/null +++ b/lib/asan/lit_tests/malloc_hook.cc @@ -0,0 +1,24 @@ +// RUN: %clangxx_asan -O2 %s -o %t +// RUN: %t 2>&1 | FileCheck %s +#include <stdlib.h> +#include <unistd.h> + +extern "C" { +// Note: avoid calling functions that allocate memory in malloc/free +// to avoid infinite recursion. +void __asan_malloc_hook(void *ptr, size_t sz) { + write(1, "MallocHook\n", sizeof("MallocHook\n")); +} +void __asan_free_hook(void *ptr) { + write(1, "FreeHook\n", sizeof("FreeHook\n")); +} +} // extern "C" + +int main() { + volatile int *x = new int; + // CHECK: MallocHook + *x = 0; + delete x; + // CHECK: FreeHook + return 0; +} diff --git a/lib/asan/lit_tests/memcmp_test.cc b/lib/asan/lit_tests/memcmp_test.cc new file mode 100644 index 000000000000..ac3f7f32ea75 --- /dev/null +++ b/lib/asan/lit_tests/memcmp_test.cc @@ -0,0 +1,19 @@ +// RUN: %clangxx_asan -m64 -O0 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m64 -O1 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m64 -O2 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m64 -O3 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m32 -O0 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m32 -O1 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m32 -O2 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m32 -O3 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s + +#include <string.h> +int main(int argc, char **argv) { + char a1[] = {argc, 2, 3, 4}; + char a2[] = {1, 2*argc, 3, 4}; + int res = memcmp(a1, a2, 4 + argc); // BOOM + // CHECK: AddressSanitizer: stack-buffer-overflow + // CHECK: {{#0.*memcmp}} + // CHECK: {{#1.*main}} + return res; +} diff --git a/lib/asan/lit_tests/null_deref.cc b/lib/asan/lit_tests/null_deref.cc new file mode 100644 index 000000000000..60a521d1c210 --- /dev/null +++ b/lib/asan/lit_tests/null_deref.cc @@ -0,0 +1,31 @@ +// RUN: %clangxx_asan -m64 -O0 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out +// RUN: %clangxx_asan -m64 -O1 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out +// RUN: %clangxx_asan -m64 -O2 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out +// RUN: %clangxx_asan -m64 -O3 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out +// RUN: %clangxx_asan -m32 -O0 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out +// RUN: %clangxx_asan -m32 -O1 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out +// RUN: %clangxx_asan -m32 -O2 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out +// RUN: %clangxx_asan -m32 -O3 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out + +__attribute__((noinline)) +static void NullDeref(int *ptr) { + // CHECK: ERROR: AddressSanitizer: SEGV on unknown address + // CHECK: {{0x0*00028 .*pc 0x.*}} + // CHECK: {{AddressSanitizer can not provide additional info.}} + ptr[10]++; // BOOM + // atos on Mac cannot extract the symbol name correctly. + // CHECK-Linux: {{ #0 0x.* in NullDeref.*null_deref.cc:}}[[@LINE-2]] + // CHECK-Darwin: {{ #0 0x.* in .*NullDeref.*null_deref.cc:}}[[@LINE-3]] +} +int main() { + NullDeref((int*)0); + // CHECK: {{ #1 0x.* in _?main.*null_deref.cc:}}[[@LINE-1]] +} diff --git a/lib/asan/lit_tests/on_error_callback.cc b/lib/asan/lit_tests/on_error_callback.cc new file mode 100644 index 000000000000..bb94d9fb579b --- /dev/null +++ b/lib/asan/lit_tests/on_error_callback.cc @@ -0,0 +1,16 @@ +// RUN: %clangxx_asan -O2 %s -o %t && %t 2>&1 | FileCheck %s + +#include <stdio.h> +#include <stdlib.h> + +extern "C" +void __asan_on_error() { + fprintf(stderr, "__asan_on_error called"); +} + +int main() { + char *x = (char*)malloc(10 * sizeof(char)); + free(x); + return x[5]; + // CHECK: __asan_on_error called +} diff --git a/lib/asan/lit_tests/sanity_check_pure_c.c b/lib/asan/lit_tests/sanity_check_pure_c.c new file mode 100644 index 000000000000..3d830653e33e --- /dev/null +++ b/lib/asan/lit_tests/sanity_check_pure_c.c @@ -0,0 +1,19 @@ +// Sanity checking a test in pure C. +// RUN: %clang -g -fsanitize=address -O2 %s -o %t +// RUN: %t 2>&1 | %symbolize | FileCheck %s + +// Sanity checking a test in pure C with -pie. +// RUN: %clang -g -fsanitize=address -O2 %s -pie -o %t +// RUN: %t 2>&1 | %symbolize | FileCheck %s + +#include <stdlib.h> +int main() { + char *x = (char*)malloc(10 * sizeof(char)); + free(x); + return x[5]; + // CHECK: heap-use-after-free + // CHECK: free + // CHECK: main{{.*}}sanity_check_pure_c.c:[[@LINE-4]] + // CHECK: malloc + // CHECK: main{{.*}}sanity_check_pure_c.c:[[@LINE-7]] +} diff --git a/lib/asan/lit_tests/shared-lib-test.cc b/lib/asan/lit_tests/shared-lib-test.cc new file mode 100644 index 000000000000..05bf3ecdf4f9 --- /dev/null +++ b/lib/asan/lit_tests/shared-lib-test.cc @@ -0,0 +1,54 @@ +// RUN: %clangxx_asan -m64 -O0 %p/SharedLibs/shared-lib-test-so.cc \ +// RUN: -fPIC -shared -o %t-so.so +// RUN: %clangxx_asan -m64 -O0 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m64 -O1 %p/SharedLibs/shared-lib-test-so.cc \ +// RUN: -fPIC -shared -o %t-so.so +// RUN: %clangxx_asan -m64 -O1 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m64 -O2 %p/SharedLibs/shared-lib-test-so.cc \ +// RUN: -fPIC -shared -o %t-so.so +// RUN: %clangxx_asan -m64 -O2 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m64 -O3 %p/SharedLibs/shared-lib-test-so.cc \ +// RUN: -fPIC -shared -o %t-so.so +// RUN: %clangxx_asan -m64 -O3 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m32 -O0 %p/SharedLibs/shared-lib-test-so.cc \ +// RUN: -fPIC -shared -o %t-so.so +// RUN: %clangxx_asan -m32 -O0 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m32 -O1 %p/SharedLibs/shared-lib-test-so.cc \ +// RUN: -fPIC -shared -o %t-so.so +// RUN: %clangxx_asan -m32 -O1 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m32 -O2 %p/SharedLibs/shared-lib-test-so.cc \ +// RUN: -fPIC -shared -o %t-so.so +// RUN: %clangxx_asan -m32 -O2 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m32 -O3 %p/SharedLibs/shared-lib-test-so.cc \ +// RUN: -fPIC -shared -o %t-so.so +// RUN: %clangxx_asan -m32 -O3 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s + +#include <dlfcn.h> +#include <stdio.h> +#include <string.h> + +#include <string> + +using std::string; + +typedef void (fun_t)(int x); + +int main(int argc, char *argv[]) { + string path = string(argv[0]) + "-so.so"; + printf("opening %s ... \n", path.c_str()); + void *lib = dlopen(path.c_str(), RTLD_NOW); + if (!lib) { + printf("error in dlopen(): %s\n", dlerror()); + return 1; + } + fun_t *inc = (fun_t*)dlsym(lib, "inc"); + if (!inc) return 1; + printf("ok\n"); + inc(1); + inc(-1); // BOOM + // CHECK: {{.*ERROR: AddressSanitizer: global-buffer-overflow}} + // CHECK: {{READ of size 4 at 0x.* thread T0}} + // CHECK: {{ #0 0x.*}} + // CHECK: {{ #1 0x.* in _?main .*shared-lib-test.cc:}}[[@LINE-4]] + return 0; +} diff --git a/lib/asan/lit_tests/sleep_before_dying.c b/lib/asan/lit_tests/sleep_before_dying.c new file mode 100644 index 000000000000..df9eba276039 --- /dev/null +++ b/lib/asan/lit_tests/sleep_before_dying.c @@ -0,0 +1,10 @@ +// RUN: %clang -g -fsanitize=address -O2 %s -o %t +// RUN: ASAN_OPTIONS="sleep_before_dying=1" %t 2>&1 | FileCheck %s + +#include <stdlib.h> +int main() { + char *x = (char*)malloc(10 * sizeof(char)); + free(x); + return x[5]; + // CHECK: Sleeping for 1 second +} diff --git a/lib/asan/lit_tests/stack-frame-demangle.cc b/lib/asan/lit_tests/stack-frame-demangle.cc new file mode 100644 index 000000000000..7f4d59fc5838 --- /dev/null +++ b/lib/asan/lit_tests/stack-frame-demangle.cc @@ -0,0 +1,24 @@ +// Check that ASan is able to print demangled frame name even w/o +// symbolization. + +// RUN: %clangxx_asan -m64 -O0 %s -o %t && %t 2>&1 | FileCheck %s + +#include <string.h> + +namespace XXX { +struct YYY { + static int ZZZ(int x) { + char array[10]; + memset(array, 0, 10); + return array[x]; // BOOOM + // CHECK: {{ERROR: AddressSanitizer: stack-buffer-overflow}} + // CHECK: {{READ of size 1 at 0x.* thread T0}} + // CHECK: {{Address 0x.* is .* frame <XXX::YYY::ZZZ(.*)>}} + } +}; +}; + +int main(int argc, char **argv) { + int res = XXX::YYY::ZZZ(argc + 10); + return res; +} diff --git a/lib/asan/lit_tests/stack-overflow.cc b/lib/asan/lit_tests/stack-overflow.cc new file mode 100644 index 000000000000..3deb1e91de6c --- /dev/null +++ b/lib/asan/lit_tests/stack-overflow.cc @@ -0,0 +1,19 @@ +// RUN: %clangxx_asan -m64 -O0 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m64 -O1 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m64 -O2 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m64 -O3 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m32 -O0 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m32 -O1 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m32 -O2 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m32 -O3 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s + +#include <string.h> +int main(int argc, char **argv) { + char x[10]; + memset(x, 0, 10); + int res = x[argc * 10]; // BOOOM + // CHECK: {{READ of size 1 at 0x.* thread T0}} + // CHECK: {{ #0 0x.* in _?main .*stack-overflow.cc:}}[[@LINE-2]] + // CHECK: {{Address 0x.* is .* frame <main>}} + return res; +} diff --git a/lib/asan/lit_tests/stack-use-after-return.cc b/lib/asan/lit_tests/stack-use-after-return.cc new file mode 100644 index 000000000000..f8d8a1a2ae39 --- /dev/null +++ b/lib/asan/lit_tests/stack-use-after-return.cc @@ -0,0 +1,45 @@ +// XFAIL: * +// RUN: %clangxx_asan -fsanitize=use-after-return -m64 -O0 %s -o %t && \ +// RUN: %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -fsanitize=use-after-return -m64 -O1 %s -o %t && \ +// RUN: %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -fsanitize=use-after-return -m64 -O2 %s -o %t && \ +// RUN: %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -fsanitize=use-after-return -m64 -O3 %s -o %t && \ +// RUN: %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -fsanitize=use-after-return -m32 -O0 %s -o %t && \ +// RUN: %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -fsanitize=use-after-return -m32 -O1 %s -o %t && \ +// RUN: %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -fsanitize=use-after-return -m32 -O2 %s -o %t && \ +// RUN: %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -fsanitize=use-after-return -m32 -O3 %s -o %t && \ +// RUN: %t 2>&1 | %symbolize | FileCheck %s + +#include <stdio.h> + +__attribute__((noinline)) +char *Ident(char *x) { + fprintf(stderr, "1: %p\n", x); + return x; +} + +__attribute__((noinline)) +char *Func1() { + char local; + return Ident(&local); +} + +__attribute__((noinline)) +void Func2(char *x) { + fprintf(stderr, "2: %p\n", x); + *x = 1; + // CHECK: WRITE of size 1 {{.*}} thread T0 + // CHECK: #0{{.*}}Func2{{.*}}stack-use-after-return.cc:[[@LINE-2]] + // CHECK: is located {{.*}} in frame <{{.*}}Func1{{.*}}> of T0's stack +} + +int main(int argc, char **argv) { + Func2(Func1()); + return 0; +} diff --git a/lib/asan/lit_tests/strip_path_prefix.c b/lib/asan/lit_tests/strip_path_prefix.c new file mode 100644 index 000000000000..ef7bf98ab3c2 --- /dev/null +++ b/lib/asan/lit_tests/strip_path_prefix.c @@ -0,0 +1,12 @@ +// RUN: %clang -g -fsanitize=address -O2 %s -o %t +// RUN: ASAN_OPTIONS="strip_path_prefix='/'" %t 2>&1 | FileCheck %s + +#include <stdlib.h> +int main() { + char *x = (char*)malloc(10 * sizeof(char)); + free(x); + return x[5]; + // Check that paths in error report don't start with slash. + // CHECK: heap-use-after-free + // CHECK-NOT: #0 0x{{.*}} ({{[/].*}}) +} diff --git a/lib/asan/lit_tests/strncpy-overflow.cc b/lib/asan/lit_tests/strncpy-overflow.cc new file mode 100644 index 000000000000..18711843c4c8 --- /dev/null +++ b/lib/asan/lit_tests/strncpy-overflow.cc @@ -0,0 +1,40 @@ +// RUN: %clangxx_asan -m64 -O0 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out +// RUN: %clangxx_asan -m64 -O1 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out +// RUN: %clangxx_asan -m64 -O2 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out +// RUN: %clangxx_asan -m64 -O3 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out +// RUN: %clangxx_asan -m32 -O0 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out +// RUN: %clangxx_asan -m32 -O1 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out +// RUN: %clangxx_asan -m32 -O2 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out +// RUN: %clangxx_asan -m32 -O3 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out + +#include <string.h> +#include <stdlib.h> +int main(int argc, char **argv) { + char *hello = (char*)malloc(6); + strcpy(hello, "hello"); + char *short_buffer = (char*)malloc(9); + strncpy(short_buffer, hello, 10); // BOOM + // CHECK: {{WRITE of size 1 at 0x.* thread T0}} + // CHECK-Linux: {{ #0 0x.* in .*strncpy}} + // CHECK-Darwin: {{ #0 0x.* in _?wrap_strncpy}} + // CHECK: {{ #1 0x.* in _?main .*strncpy-overflow.cc:}}[[@LINE-4]] + // CHECK: {{0x.* is located 0 bytes to the right of 9-byte region}} + // CHECK: {{allocated by thread T0 here:}} + + // CHECK-Linux: {{ #0 0x.* in .*malloc}} + // CHECK-Linux: {{ #1 0x.* in main .*strncpy-overflow.cc:}}[[@LINE-10]] + + // CHECK-Darwin: {{ #0 0x.* in .*mz_malloc.*}} + // CHECK-Darwin: {{ #1 0x.* in malloc_zone_malloc.*}} + // CHECK-Darwin: {{ #2 0x.* in malloc.*}} + // CHECK-Darwin: {{ #3 0x.* in _?main .*strncpy-overflow.cc:}}[[@LINE-15]] + return short_buffer[8]; +} diff --git a/lib/asan/lit_tests/symbolize_callback.cc b/lib/asan/lit_tests/symbolize_callback.cc new file mode 100644 index 000000000000..0691d501e24d --- /dev/null +++ b/lib/asan/lit_tests/symbolize_callback.cc @@ -0,0 +1,17 @@ +// RUN: %clangxx_asan -O2 %s -o %t && %t 2>&1 | FileCheck %s + +#include <stdio.h> +#include <stdlib.h> + +extern "C" +bool __asan_symbolize(const void *pc, char *out_buffer, int out_size) { + snprintf(out_buffer, out_size, "MySymbolizer"); + return true; +} + +int main() { + char *x = (char*)malloc(10 * sizeof(char)); + free(x); + return x[5]; + // CHECK: MySymbolizer +} diff --git a/lib/asan/lit_tests/use-after-free.cc b/lib/asan/lit_tests/use-after-free.cc new file mode 100644 index 000000000000..24d5a2a54807 --- /dev/null +++ b/lib/asan/lit_tests/use-after-free.cc @@ -0,0 +1,48 @@ +// RUN: %clangxx_asan -m64 -O0 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out +// RUN: %clangxx_asan -m64 -O1 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out +// RUN: %clangxx_asan -m64 -O2 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out +// RUN: %clangxx_asan -m64 -O3 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out +// RUN: %clangxx_asan -m32 -O0 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out +// RUN: %clangxx_asan -m32 -O1 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out +// RUN: %clangxx_asan -m32 -O2 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out +// RUN: %clangxx_asan -m32 -O3 %s -o %t && %t 2>&1 | %symbolize > %t.out +// RUN: FileCheck %s < %t.out && FileCheck %s --check-prefix=CHECK-%os < %t.out + +#include <stdlib.h> +int main() { + char *x = (char*)malloc(10 * sizeof(char)); + free(x); + return x[5]; + // CHECK: {{.*ERROR: AddressSanitizer: heap-use-after-free on address}} + // CHECK: {{0x.* at pc 0x.* bp 0x.* sp 0x.*}} + // CHECK: {{READ of size 1 at 0x.* thread T0}} + // CHECK: {{ #0 0x.* in _?main .*use-after-free.cc:22}} + // CHECK: {{0x.* is located 5 bytes inside of 10-byte region .0x.*,0x.*}} + // CHECK: {{freed by thread T0 here:}} + + // CHECK-Linux: {{ #0 0x.* in .*free}} + // CHECK-Linux: {{ #1 0x.* in main .*use-after-free.cc:21}} + + // CHECK-Darwin: {{ #0 0x.* in .*free_common.*}} + // CHECK-Darwin: {{ #1 0x.* in .*mz_free.*}} + // We override free() on Darwin, thus no malloc_zone_free + // CHECK-Darwin: {{ #2 0x.* in _?wrap_free}} + // CHECK-Darwin: {{ #3 0x.* in _?main .*use-after-free.cc:21}} + + // CHECK: {{previously allocated by thread T0 here:}} + + // CHECK-Linux: {{ #0 0x.* in .*malloc}} + // CHECK-Linux: {{ #1 0x.* in main .*use-after-free.cc:20}} + + // CHECK-Darwin: {{ #0 0x.* in .*mz_malloc.*}} + // CHECK-Darwin: {{ #1 0x.* in malloc_zone_malloc.*}} + // CHECK-Darwin: {{ #2 0x.* in malloc.*}} + // CHECK-Darwin: {{ #3 0x.* in _?main .*use-after-free.cc:20}} +} diff --git a/lib/asan/lit_tests/use-after-scope-inlined.cc b/lib/asan/lit_tests/use-after-scope-inlined.cc new file mode 100644 index 000000000000..3d730de6ab35 --- /dev/null +++ b/lib/asan/lit_tests/use-after-scope-inlined.cc @@ -0,0 +1,29 @@ +// Test with "-O2" only to make sure inlining (leading to use-after-scope) +// happens. "always_inline" is not enough, as Clang doesn't emit +// llvm.lifetime intrinsics at -O0. +// +// RUN: %clangxx_asan -m64 -O2 -fsanitize=use-after-scope %s -o %t && \ +// RUN: %t 2>&1 | %symbolize | FileCheck %s +// RUN: %clangxx_asan -m32 -O2 -fsanitize=use-after-scope %s -o %t && \ +// RUN: %t 2>&1 | %symbolize | FileCheck %s + +int *arr; + +__attribute__((always_inline)) +void inlined(int arg) { + int x[5]; + for (int i = 0; i < arg; i++) x[i] = i; + arr = x; +} + +int main(int argc, char *argv[]) { + inlined(argc); + return arr[argc - 1]; // BOOM + // CHECK: ERROR: AddressSanitizer: stack-use-after-scope + // CHECK: READ of size 4 at 0x{{.*}} thread T0 + // CHECK: #0 0x{{.*}} in {{_?}}main + // CHECK: {{.*}}use-after-scope-inlined.cc:[[@LINE-4]] + // CHECK: Address 0x{{.*}} is located at offset + // CHECK: [[OFFSET:[^ ]*]] in frame <main> of T0{{.*}}: + // CHECK: {{\[}}[[OFFSET]], {{.*}}) 'x.i' +} diff --git a/lib/asan/output_tests/clone_test.cc b/lib/asan/output_tests/clone_test.cc deleted file mode 100644 index b18d2550bc7c..000000000000 --- a/lib/asan/output_tests/clone_test.cc +++ /dev/null @@ -1,34 +0,0 @@ -#ifdef __linux__ -#include <stdio.h> -#include <sched.h> -#include <sys/syscall.h> -#include <sys/types.h> -#include <sys/wait.h> -#include <unistd.h> - -int Child(void *arg) { - char x[32] = {0}; // Stack gets poisoned. - printf("Child: %p\n", x); - _exit(1); // NoReturn, stack will remain unpoisoned unless we do something. -} - -int main(int argc, char **argv) { - const int kStackSize = 1 << 20; - char child_stack[kStackSize + 1]; - char *sp = child_stack + kStackSize; // Stack grows down. - printf("Parent: %p\n", sp); - pid_t clone_pid = clone(Child, sp, CLONE_FILES | CLONE_VM, NULL, 0, 0, 0); - waitpid(clone_pid, NULL, 0); - for (int i = 0; i < kStackSize; i++) - child_stack[i] = i; - int ret = child_stack[argc - 1]; - printf("PASSED\n"); - return ret; -} -#else // not __linux__ -#include <stdio.h> -int main() { - printf("PASSED\n"); - // Check-Common: PASSED -} -#endif diff --git a/lib/asan/output_tests/deep_tail_call.cc b/lib/asan/output_tests/deep_tail_call.cc deleted file mode 100644 index cb69e8925197..000000000000 --- a/lib/asan/output_tests/deep_tail_call.cc +++ /dev/null @@ -1,15 +0,0 @@ -// Check-Common: AddressSanitizer global-buffer-overflow -int global[10]; -// Check-Common: {{#0.*call4}} -void __attribute__((noinline)) call4(int i) { global[i+10]++; } -// Check-Common: {{#1.*call3}} -void __attribute__((noinline)) call3(int i) { call4(i); } -// Check-Common: {{#2.*call2}} -void __attribute__((noinline)) call2(int i) { call3(i); } -// Check-Common: {{#3.*call1}} -void __attribute__((noinline)) call1(int i) { call2(i); } -// Check-Common: {{#4.*main}} -int main(int argc, char **argv) { - call1(argc); - return global[0]; -} diff --git a/lib/asan/output_tests/global-overflow.cc b/lib/asan/output_tests/global-overflow.cc deleted file mode 100644 index a63eb733365f..000000000000 --- a/lib/asan/output_tests/global-overflow.cc +++ /dev/null @@ -1,16 +0,0 @@ -#include <string.h> -int main(int argc, char **argv) { - static char XXX[10]; - static char YYY[10]; - static char ZZZ[10]; - memset(XXX, 0, 10); - memset(YYY, 0, 10); - memset(ZZZ, 0, 10); - int res = YYY[argc * 10]; // BOOOM - // Check-Common: {{READ of size 1 at 0x.* thread T0}} - // Check-Common: {{ #0 0x.* in main .*global-overflow.cc:9}} - // Check-Common: {{0x.* is located 0 bytes to the right of global variable}} - // Check-Common: {{.*YYY.* of size 10}} - res += XXX[argc] + ZZZ[argc]; - return res; -} diff --git a/lib/asan/output_tests/heap-overflow.cc b/lib/asan/output_tests/heap-overflow.cc deleted file mode 100644 index 534fbe00b355..000000000000 --- a/lib/asan/output_tests/heap-overflow.cc +++ /dev/null @@ -1,22 +0,0 @@ -#include <stdlib.h> -#include <string.h> -int main(int argc, char **argv) { - char *x = (char*)malloc(10 * sizeof(char)); - memset(x, 0, 10); - int res = x[argc * 10]; // BOOOM - free(x); - return res; -} - -// Check-Common: {{READ of size 1 at 0x.* thread T0}} -// Check-Common: {{ #0 0x.* in main .*heap-overflow.cc:6}} -// Check-Common: {{0x.* is located 0 bytes to the right of 10-byte region}} -// Check-Common: {{allocated by thread T0 here:}} - -// Check-Linux: {{ #0 0x.* in .*malloc}} -// Check-Linux: {{ #1 0x.* in main .*heap-overflow.cc:4}} - -// Check-Darwin: {{ #0 0x.* in .*mz_malloc.*}} -// Check-Darwin: {{ #1 0x.* in malloc_zone_malloc.*}} -// Check-Darwin: {{ #2 0x.* in malloc.*}} -// Check-Darwin: {{ #3 0x.* in main heap-overflow.cc:4}} diff --git a/lib/asan/output_tests/interception_failure_test-linux.cc b/lib/asan/output_tests/interception_failure_test-linux.cc deleted file mode 100644 index 9e8b7536906b..000000000000 --- a/lib/asan/output_tests/interception_failure_test-linux.cc +++ /dev/null @@ -1,17 +0,0 @@ -#include <stdlib.h> -#include <stdio.h> - -extern "C" long strtol(const char *nptr, char **endptr, int base) { - fprintf(stderr, "my_strtol_interceptor\n"); - return 0; -} - -int main() { - char *x = (char*)malloc(10 * sizeof(char)); - free(x); - return (int)strtol(x, 0, 10); -} - -// Check-Common: my_strtol_interceptor -// CHECK-NOT: heap-use-after-free - diff --git a/lib/asan/output_tests/interception_malloc_test-linux.cc b/lib/asan/output_tests/interception_malloc_test-linux.cc deleted file mode 100644 index 4bb3bd66de33..000000000000 --- a/lib/asan/output_tests/interception_malloc_test-linux.cc +++ /dev/null @@ -1,19 +0,0 @@ -#include <stdlib.h> -#include <stdio.h> -#include <unistd.h> - -extern "C" void *__interceptor_malloc(size_t size); -extern "C" void *malloc(size_t size) { - write(2, "malloc call\n", sizeof("malloc call\n") - 1); - return __interceptor_malloc(size); -} - -int main() { - char *x = (char*)malloc(10 * sizeof(char)); - free(x); - return (int)strtol(x, 0, 10); -} - -// Check-Common: malloc call -// Check-Common: heap-use-after-free - diff --git a/lib/asan/output_tests/interception_test-linux.cc b/lib/asan/output_tests/interception_test-linux.cc deleted file mode 100644 index 0523510465a1..000000000000 --- a/lib/asan/output_tests/interception_test-linux.cc +++ /dev/null @@ -1,18 +0,0 @@ -#include <stdlib.h> -#include <stdio.h> - -extern "C" long __interceptor_strtol(const char *nptr, char **endptr, int base); -extern "C" long strtol(const char *nptr, char **endptr, int base) { - fprintf(stderr, "my_strtol_interceptor\n"); - return __interceptor_strtol(nptr, endptr, base); -} - -int main() { - char *x = (char*)malloc(10 * sizeof(char)); - free(x); - return (int)strtol(x, 0, 10); -} - -// Check-Common: my_strtol_interceptor -// Check-Common: heap-use-after-free - diff --git a/lib/asan/output_tests/large_func_test.cc b/lib/asan/output_tests/large_func_test.cc deleted file mode 100644 index 49751b39277a..000000000000 --- a/lib/asan/output_tests/large_func_test.cc +++ /dev/null @@ -1,48 +0,0 @@ -#include <stdlib.h> -__attribute__((noinline)) -static void LargeFunction(int *x, int zero) { - x[0]++; - x[1]++; - x[2]++; - x[3]++; - x[4]++; - x[5]++; - x[6]++; - x[7]++; - x[8]++; - x[9]++; - - x[zero + 111]++; // we should report this exact line - - x[10]++; - x[11]++; - x[12]++; - x[13]++; - x[14]++; - x[15]++; - x[16]++; - x[17]++; - x[18]++; - x[19]++; -} - -int main(int argc, char **argv) { - int *x = new int[100]; - LargeFunction(x, argc - 1); - delete x; -} - -// Check-Common: {{.*ERROR: AddressSanitizer heap-buffer-overflow on address}} -// Check-Common: {{0x.* at pc 0x.* bp 0x.* sp 0x.*}} -// Check-Common: {{READ of size 4 at 0x.* thread T0}} - -// atos incorrectly extracts the symbol name for the static functions on -// Darwin. -// Check-Linux: {{ #0 0x.* in LargeFunction.*large_func_test.cc:15}} -// Check-Darwin: {{ #0 0x.* in .*LargeFunction.*large_func_test.cc:15}} - -// Check-Common: {{ #1 0x.* in main .*large_func_test.cc:31}} -// Check-Common: {{0x.* is located 44 bytes to the right of 400-byte region}} -// Check-Common: {{allocated by thread T0 here:}} -// Check-Common: {{ #0 0x.* in operator new.*}} -// Check-Common: {{ #1 0x.* in main .*large_func_test.cc:30}} diff --git a/lib/asan/output_tests/memcmp_test.cc b/lib/asan/output_tests/memcmp_test.cc deleted file mode 100644 index d0e5a43b4355..000000000000 --- a/lib/asan/output_tests/memcmp_test.cc +++ /dev/null @@ -1,10 +0,0 @@ -#include <string.h> -int main(int argc, char **argv) { - char a1[] = {argc, 2, 3, 4}; - char a2[] = {1, 2*argc, 3, 4}; -// Check-Common: AddressSanitizer stack-buffer-overflow -// Check-Common: {{#0.*memcmp}} -// Check-Common: {{#1.*main}} - int res = memcmp(a1, a2, 4 + argc); // BOOM - return res; -} diff --git a/lib/asan/output_tests/null_deref.cc b/lib/asan/output_tests/null_deref.cc deleted file mode 100644 index c152a4202e33..000000000000 --- a/lib/asan/output_tests/null_deref.cc +++ /dev/null @@ -1,17 +0,0 @@ -__attribute__((noinline)) -static void NullDeref(int *ptr) { - ptr[10]++; -} -int main() { - NullDeref((int*)0); -} - -// Check-Common: {{.*ERROR: AddressSanitizer crashed on unknown address}} -// Check-Common: {{0x0*00028 .*pc 0x.*}} -// Check-Common: {{AddressSanitizer can not provide additional info. ABORTING}} - -// atos on Mac cannot extract the symbol name correctly. -// Check-Linux: {{ #0 0x.* in NullDeref.*null_deref.cc:3}} -// Check-Darwin: {{ #0 0x.* in .*NullDeref.*null_deref.cc:3}} - -// Check-Common: {{ #1 0x.* in main.*null_deref.cc:6}} diff --git a/lib/asan/output_tests/shared-lib-test.cc b/lib/asan/output_tests/shared-lib-test.cc deleted file mode 100644 index 060fcde35f0d..000000000000 --- a/lib/asan/output_tests/shared-lib-test.cc +++ /dev/null @@ -1,42 +0,0 @@ -//===----------- shared-lib-test.cc -----------------------------*- C++ -*-===// -// -// 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. -// -//===----------------------------------------------------------------------===// -#include <dlfcn.h> -#include <stdio.h> -#include <string.h> - -#include <string> - -using std::string; - -typedef void (fun_t)(int x); - -int main(int argc, char *argv[]) { - string path = string(argv[0]) + "-so.so"; - printf("opening %s ... \n", path.c_str()); - void *lib = dlopen(path.c_str(), RTLD_NOW); - if (!lib) { - printf("error in dlopen(): %s\n", dlerror()); - return 1; - } - fun_t *inc = (fun_t*)dlsym(lib, "inc"); - if (!inc) return 1; - printf("ok\n"); - inc(1); - inc(-1); // BOOM - return 0; -} - -// Check-Common: {{.*ERROR: AddressSanitizer global-buffer-overflow}} -// Check-Common: {{READ of size 4 at 0x.* thread T0}} -// Check-Common: {{ #0 0x.*}} -// Check-Common: {{ #1 0x.* in main .*shared-lib-test.cc:35}} diff --git a/lib/asan/output_tests/stack-overflow.cc b/lib/asan/output_tests/stack-overflow.cc deleted file mode 100644 index 35fa8a66c34c..000000000000 --- a/lib/asan/output_tests/stack-overflow.cc +++ /dev/null @@ -1,11 +0,0 @@ -#include <string.h> -int main(int argc, char **argv) { - char x[10]; - memset(x, 0, 10); - int res = x[argc * 10]; // BOOOM - return res; -} - -// Check-Common: {{READ of size 1 at 0x.* thread T0}} -// Check-Common: {{ #0 0x.* in main .*stack-overflow.cc:5}} -// Check-Common: {{Address 0x.* is .* frame <main>}} diff --git a/lib/asan/output_tests/stack-use-after-return.cc.disabled b/lib/asan/output_tests/stack-use-after-return.cc.disabled deleted file mode 100644 index f49715737430..000000000000 --- a/lib/asan/output_tests/stack-use-after-return.cc.disabled +++ /dev/null @@ -1,27 +0,0 @@ -#include <stdio.h> - -__attribute__((noinline)) -char *Ident(char *x) { - fprintf(stderr, "1: %p\n", x); - return x; -} - -__attribute__((noinline)) -char *Func1() { - char local; - return Ident(&local); -} - -__attribute__((noinline)) -void Func2(char *x) { - fprintf(stderr, "2: %p\n", x); - *x = 1; - // Check-Common: {{WRITE of size 1 .* thread T0}} - // Check-Common: {{ #0.*Func2.*stack-use-after-return.cc:18}} - // Check-Common: {{is located in frame <.*Func1.*> of T0's stack}} -} - -int main(int argc, char **argv) { - Func2(Func1()); - return 0; -} diff --git a/lib/asan/output_tests/strncpy-overflow.cc b/lib/asan/output_tests/strncpy-overflow.cc deleted file mode 100644 index 66d5810b7040..000000000000 --- a/lib/asan/output_tests/strncpy-overflow.cc +++ /dev/null @@ -1,24 +0,0 @@ -#include <string.h> -#include <stdlib.h> -int main(int argc, char **argv) { - char *hello = (char*)malloc(6); - strcpy(hello, "hello"); - char *short_buffer = (char*)malloc(9); - strncpy(short_buffer, hello, 10); // BOOM - return short_buffer[8]; -} - -// Check-Common: {{WRITE of size 1 at 0x.* thread T0}} -// Check-Linux: {{ #0 0x.* in .*strncpy}} -// Check-Darwin: {{ #0 0x.* in wrap_strncpy}} -// Check-Common: {{ #1 0x.* in main .*strncpy-overflow.cc:7}} -// Check-Common: {{0x.* is located 0 bytes to the right of 9-byte region}} -// Check-Common: {{allocated by thread T0 here:}} - -// Check-Linux: {{ #0 0x.* in .*malloc}} -// Check-Linux: {{ #1 0x.* in main .*strncpy-overflow.cc:6}} - -// Check-Darwin: {{ #0 0x.* in .*mz_malloc.*}} -// Check-Darwin: {{ #1 0x.* in malloc_zone_malloc.*}} -// Check-Darwin: {{ #2 0x.* in malloc.*}} -// Check-Darwin: {{ #3 0x.* in main .*strncpy-overflow.cc:6}} diff --git a/lib/asan/output_tests/test_output.sh b/lib/asan/output_tests/test_output.sh deleted file mode 100755 index 6510043396e4..000000000000 --- a/lib/asan/output_tests/test_output.sh +++ /dev/null @@ -1,79 +0,0 @@ -#!/bin/bash - -set -e # fail on any error - -OS=`uname` -CXX=$1 -CC=$2 -FILE_CHECK=$3 -CXXFLAGS="-mno-omit-leaf-frame-pointer -fno-omit-frame-pointer -fno-optimize-sibling-calls -g" -SYMBOLIZER=../scripts/asan_symbolize.py -TMP_ASAN_REPORT=asan_report.tmp - -run_program() { - ./$1 2>&1 | $SYMBOLIZER 2> /dev/null | c++filt > $TMP_ASAN_REPORT -} - -# check_program exe_file source_file check_prefixf -check_program() { - run_program $1 - $FILE_CHECK $2 --check-prefix=$3 < $TMP_ASAN_REPORT - rm -f $TMP_ASAN_REPORT -} - -C_TEST=use-after-free -echo "Sanity checking a test in pure C" -$CC -g -faddress-sanitizer -O2 $C_TEST.c -check_program a.out $C_TEST.c CHECK -rm ./a.out - -echo "Sanity checking a test in pure C with -pie" -$CC -g -faddress-sanitizer -O2 $C_TEST.c -pie -check_program a.out $C_TEST.c CHECK -rm ./a.out - -echo "Testing sleep_before_dying" -$CC -g -faddress-sanitizer -O2 $C_TEST.c -export ASAN_OPTIONS="sleep_before_dying=1" -check_program a.out $C_TEST.c CHECKSLEEP -export ASAN_OPTIONS="" -rm ./a.out - -# FIXME: some tests do not need to be ran for all the combinations of arch -# and optimization mode. -for t in *.cc; do - for b in 32 64; do - for O in 0 1 2 3; do - c=`basename $t .cc` - if [[ "$c" == *"-so" ]]; then - continue - fi - if [[ "$c" == *"-linux" ]]; then - if [[ "$OS" != "Linux" ]]; then - continue - fi - fi - c_so=$c-so - exe=$c.$b.O$O - so=$c.$b.O$O-so.so - echo testing $exe - build_command="$CXX $CXXFLAGS -m$b -faddress-sanitizer -O$O $c.cc -o $exe" - [ "$DEBUG" == "1" ] && echo $build_command - $build_command - [ -e "$c_so.cc" ] && $CXX $CXXFLAGS -m$b -faddress-sanitizer -O$O $c_so.cc -fPIC -shared -o $so - run_program $exe - # Check common expected lines for OS. - $FILE_CHECK $c.cc --check-prefix="Check-Common" < $TMP_ASAN_REPORT - # Check OS-specific lines. - if [ `grep -c "Check-$OS" $c.cc` -gt 0 ] - then - $FILE_CHECK $c.cc --check-prefix="Check-$OS" < $TMP_ASAN_REPORT - fi - rm ./$exe - rm ./$TMP_ASAN_REPORT - [ -e "$so" ] && rm ./$so - done - done -done - -exit 0 diff --git a/lib/asan/output_tests/use-after-free.c b/lib/asan/output_tests/use-after-free.c deleted file mode 100644 index 801d3f68a466..000000000000 --- a/lib/asan/output_tests/use-after-free.c +++ /dev/null @@ -1,9 +0,0 @@ -#include <stdlib.h> -int main() { - char *x = (char*)malloc(10 * sizeof(char)); - free(x); - return x[5]; -} - -// CHECK: heap-use-after-free -// CHECKSLEEP: Sleeping for 1 second diff --git a/lib/asan/output_tests/use-after-free.cc b/lib/asan/output_tests/use-after-free.cc deleted file mode 100644 index c3e9dbe6db75..000000000000 --- a/lib/asan/output_tests/use-after-free.cc +++ /dev/null @@ -1,31 +0,0 @@ -#include <stdlib.h> -int main() { - char *x = (char*)malloc(10 * sizeof(char)); - free(x); - return x[5]; -} - -// Check-Common: {{.*ERROR: AddressSanitizer heap-use-after-free on address}} -// Check-Common: {{0x.* at pc 0x.* bp 0x.* sp 0x.*}} -// Check-Common: {{READ of size 1 at 0x.* thread T0}} -// Check-Common: {{ #0 0x.* in main .*use-after-free.cc:5}} -// Check-Common: {{0x.* is located 5 bytes inside of 10-byte region .0x.*,0x.*}} -// Check-Common: {{freed by thread T0 here:}} - -// Check-Linux: {{ #0 0x.* in .*free}} -// Check-Linux: {{ #1 0x.* in main .*use-after-free.cc:4}} - -// Check-Darwin: {{ #0 0x.* in .*mz_free.*}} -// We override free() on Darwin, thus no malloc_zone_free -// Check-Darwin: {{ #1 0x.* in wrap_free}} -// Check-Darwin: {{ #2 0x.* in main .*use-after-free.cc:4}} - -// Check-Common: {{previously allocated by thread T0 here:}} - -// Check-Linux: {{ #0 0x.* in .*malloc}} -// Check-Linux: {{ #1 0x.* in main .*use-after-free.cc:3}} - -// Check-Darwin: {{ #0 0x.* in .*mz_malloc.*}} -// Check-Darwin: {{ #1 0x.* in malloc_zone_malloc.*}} -// Check-Darwin: {{ #2 0x.* in malloc.*}} -// Check-Darwin: {{ #3 0x.* in main .*use-after-free.cc:3}} diff --git a/lib/asan/scripts/asan_symbolize.py b/lib/asan/scripts/asan_symbolize.py index e4897d0c7649..7b30bb55914e 100755 --- a/lib/asan/scripts/asan_symbolize.py +++ b/lib/asan/scripts/asan_symbolize.py @@ -7,121 +7,350 @@ # License. See LICENSE.TXT for details. # #===------------------------------------------------------------------------===# +import bisect import os import re -import sys -import string import subprocess +import sys -pipes = {} +llvm_symbolizer = None +symbolizers = {} filetypes = {} -DEBUG=False +vmaddrs = {} +DEBUG = False + +# FIXME: merge the code that calls fix_filename(). def fix_filename(file_name): for path_to_cut in sys.argv[1:]: - file_name = re.sub(".*" + path_to_cut, "", file_name) - file_name = re.sub(".*asan_[a-z_]*.cc:[0-9]*", "_asan_rtl_", file_name) - file_name = re.sub(".*crtstuff.c:0", "???:0", file_name) + file_name = re.sub('.*' + path_to_cut, '', file_name) + file_name = re.sub('.*asan_[a-z_]*.cc:[0-9]*', '_asan_rtl_', file_name) + file_name = re.sub('.*crtstuff.c:0', '???:0', file_name) return file_name -# TODO(glider): need some refactoring here -def symbolize_addr2line(line): - #0 0x7f6e35cf2e45 (/blah/foo.so+0x11fe45) - match = re.match('^( *#([0-9]+) *0x[0-9a-f]+) *\((.*)\+(0x[0-9a-f]+)\)', line) - if match: - frameno = match.group(2) - binary = match.group(3) - addr = match.group(4) - if not pipes.has_key(binary): - pipes[binary] = subprocess.Popen(["addr2line", "-f", "-e", binary], - stdin=subprocess.PIPE, stdout=subprocess.PIPE) - p = pipes[binary] +class Symbolizer(object): + def __init__(self): + pass + + def symbolize(self, addr, binary, offset): + """Symbolize the given address (pair of binary and offset). + + Overriden in subclasses. + Args: + addr: virtual address of an instruction. + binary: path to executable/shared object containing this instruction. + offset: instruction offset in the @binary. + Returns: + list of strings (one string for each inlined frame) describing + the code locations for this instruction (that is, function name, file + name, line and column numbers). + """ + return None + + +class LLVMSymbolizer(Symbolizer): + def __init__(self, symbolizer_path): + super(LLVMSymbolizer, self).__init__() + self.symbolizer_path = symbolizer_path + self.pipe = self.open_llvm_symbolizer() + + def open_llvm_symbolizer(self): + if not os.path.exists(self.symbolizer_path): + return None + cmd = [self.symbolizer_path, + '--use-symbol-table=true', + '--demangle=false', + '--functions=true', + '--inlining=true'] + if DEBUG: + print ' '.join(cmd) + return subprocess.Popen(cmd, stdin=subprocess.PIPE, + stdout=subprocess.PIPE) + + def symbolize(self, addr, binary, offset): + """Overrides Symbolizer.symbolize.""" + if not self.pipe: + return None + result = [] try: - print >>p.stdin, addr - function_name = p.stdout.readline().rstrip() - file_name = p.stdout.readline().rstrip() - except: - function_name = "" - file_name = "" - file_name = fix_filename(file_name) + symbolizer_input = '%s %s' % (binary, offset) + if DEBUG: + print symbolizer_input + print >> self.pipe.stdin, symbolizer_input + while True: + function_name = self.pipe.stdout.readline().rstrip() + if not function_name: + break + file_name = self.pipe.stdout.readline().rstrip() + file_name = fix_filename(file_name) + if (not function_name.startswith('??') and + not file_name.startswith('??')): + # Append only valid frames. + result.append('%s in %s %s' % (addr, function_name, + file_name)) + except Exception: + result = [] + if not result: + result = None + return result - print match.group(1), "in", function_name, file_name - else: - print line.rstrip() - - -def get_macho_filetype(binary): - if not filetypes.has_key(binary): - otool_pipe = subprocess.Popen(["otool", "-Vh", binary], - stdin=subprocess.PIPE, stdout=subprocess.PIPE) - otool_line = "".join(otool_pipe.stdout.readlines()) - for t in ["DYLIB", "EXECUTE"]: - if t in otool_line: - filetypes[binary] = t - otool_pipe.stdin.close() - return filetypes[binary] - - -def symbolize_atos(line): - #0 0x7f6e35cf2e45 (/blah/foo.so+0x11fe45) - match = re.match('^( *#([0-9]+) *)(0x[0-9a-f]+) *\((.*)\+(0x[0-9a-f]+)\)', line) - if match: - #print line - prefix = match.group(1) - frameno = match.group(2) - orig_addr = match.group(3) - binary = match.group(4) - offset = match.group(5) - addr = orig_addr - load_addr = hex(int(orig_addr, 16) - int(offset, 16)) - filetype = get_macho_filetype(binary) - - if not pipes.has_key(binary): - # Guess which arch we're running. 10 = len("0x") + 8 hex digits. - if len(addr) > 10: - arch = "x86_64" - else: - arch = "i386" - if filetype == "DYLIB": - load_addr = "0x0" +def LLVMSymbolizerFactory(system): + symbolizer_path = os.getenv('LLVM_SYMBOLIZER_PATH') + if not symbolizer_path: + # Assume llvm-symbolizer is in PATH. + symbolizer_path = 'llvm-symbolizer' + return LLVMSymbolizer(symbolizer_path) + + +class Addr2LineSymbolizer(Symbolizer): + def __init__(self, binary): + super(Addr2LineSymbolizer, self).__init__() + self.binary = binary + self.pipe = self.open_addr2line() + + def open_addr2line(self): + cmd = ['addr2line', '-f', '-e', self.binary] if DEBUG: - print "atos -o %s -arch %s -l %s" % (binary, arch, load_addr) - cmd = ["atos", "-o", binary, "-arch", arch, "-l", load_addr] - pipes[binary] = subprocess.Popen(cmd, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - p = pipes[binary] - if filetype == "DYLIB": - print >>p.stdin, "%s" % offset + print ' '.join(cmd) + return subprocess.Popen(cmd, + stdin=subprocess.PIPE, stdout=subprocess.PIPE) + + def symbolize(self, addr, binary, offset): + """Overrides Symbolizer.symbolize.""" + if self.binary != binary: + return None + try: + print >> self.pipe.stdin, offset + function_name = self.pipe.stdout.readline().rstrip() + file_name = self.pipe.stdout.readline().rstrip() + except Exception: + function_name = '' + file_name = '' + file_name = fix_filename(file_name) + return ['%s in %s %s' % (addr, function_name, file_name)] + + +class DarwinSymbolizer(Symbolizer): + def __init__(self, addr, binary): + super(DarwinSymbolizer, self).__init__() + self.binary = binary + # Guess which arch we're running. 10 = len('0x') + 8 hex digits. + if len(addr) > 10: + self.arch = 'x86_64' else: - print >>p.stdin, "%s" % addr - # TODO(glider): it's more efficient to make a batch atos run for each binary. - p.stdin.close() - atos_line = p.stdout.readline().rstrip() + self.arch = 'i386' + self.vmaddr = None + self.pipe = None + + def write_addr_to_pipe(self, offset): + print >> self.pipe.stdin, '0x%x' % int(offset, 16) + + def open_atos(self): + if DEBUG: + print 'atos -o %s -arch %s' % (self.binary, self.arch) + cmdline = ['atos', '-o', self.binary, '-arch', self.arch] + self.pipe = subprocess.Popen(cmdline, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + + def symbolize(self, addr, binary, offset): + """Overrides Symbolizer.symbolize.""" + if self.binary != binary: + return None + self.open_atos() + self.write_addr_to_pipe(offset) + self.pipe.stdin.close() + atos_line = self.pipe.stdout.readline().rstrip() # A well-formed atos response looks like this: # foo(type1, type2) (in object.name) (filename.cc:80) match = re.match('^(.*) \(in (.*)\) \((.*:\d*)\)$', atos_line) - #print "atos_line: ", atos_line + if DEBUG: + print 'atos_line: ', atos_line if match: function_name = match.group(1) - function_name = re.sub("\(.*?\)", "", function_name) + function_name = re.sub('\(.*?\)', '', function_name) file_name = fix_filename(match.group(3)) - print "%s%s in %s %s" % (prefix, addr, function_name, file_name) + return ['%s in %s %s' % (addr, function_name, file_name)] + else: + return ['%s in %s' % (addr, atos_line)] + + +# Chain several symbolizers so that if one symbolizer fails, we fall back +# to the next symbolizer in chain. +class ChainSymbolizer(Symbolizer): + def __init__(self, symbolizer_list): + super(ChainSymbolizer, self).__init__() + self.symbolizer_list = symbolizer_list + + def symbolize(self, addr, binary, offset): + """Overrides Symbolizer.symbolize.""" + for symbolizer in self.symbolizer_list: + if symbolizer: + result = symbolizer.symbolize(addr, binary, offset) + if result: + return result + return None + + def append_symbolizer(self, symbolizer): + self.symbolizer_list.append(symbolizer) + + +def BreakpadSymbolizerFactory(binary): + suffix = os.getenv('BREAKPAD_SUFFIX') + if suffix: + filename = binary + suffix + if os.access(filename, os.F_OK): + return BreakpadSymbolizer(filename) + return None + + +def SystemSymbolizerFactory(system, addr, binary): + if system == 'Darwin': + return DarwinSymbolizer(addr, binary) + elif system == 'Linux': + return Addr2LineSymbolizer(binary) + + +class BreakpadSymbolizer(Symbolizer): + def __init__(self, filename): + super(BreakpadSymbolizer, self).__init__() + self.filename = filename + lines = file(filename).readlines() + self.files = [] + self.symbols = {} + self.address_list = [] + self.addresses = {} + # MODULE mac x86_64 A7001116478B33F18FF9BEDE9F615F190 t + fragments = lines[0].rstrip().split() + self.arch = fragments[2] + self.debug_id = fragments[3] + self.binary = ' '.join(fragments[4:]) + self.parse_lines(lines[1:]) + + def parse_lines(self, lines): + cur_function_addr = '' + for line in lines: + fragments = line.split() + if fragments[0] == 'FILE': + assert int(fragments[1]) == len(self.files) + self.files.append(' '.join(fragments[2:])) + elif fragments[0] == 'PUBLIC': + self.symbols[int(fragments[1], 16)] = ' '.join(fragments[3:]) + elif fragments[0] in ['CFI', 'STACK']: + pass + elif fragments[0] == 'FUNC': + cur_function_addr = int(fragments[1], 16) + if not cur_function_addr in self.symbols.keys(): + self.symbols[cur_function_addr] = ' '.join(fragments[4:]) + else: + # Line starting with an address. + addr = int(fragments[0], 16) + self.address_list.append(addr) + # Tuple of symbol address, size, line, file number. + self.addresses[addr] = (cur_function_addr, + int(fragments[1], 16), + int(fragments[2]), + int(fragments[3])) + self.address_list.sort() + + def get_sym_file_line(self, addr): + key = None + if addr in self.addresses.keys(): + key = addr else: - print "%s%s in %s" % (prefix, addr, atos_line) - del pipes[binary] - else: - print line.rstrip() - -system = os.uname()[0] -if system in ['Linux', 'Darwin']: - for line in sys.stdin: - if system == 'Linux': - symbolize_addr2line(line) - elif system == 'Darwin': - symbolize_atos(line) -else: - print 'Unknown system: ', system + index = bisect.bisect_left(self.address_list, addr) + if index == 0: + return None + else: + key = self.address_list[index - 1] + sym_id, size, line_no, file_no = self.addresses[key] + symbol = self.symbols[sym_id] + filename = self.files[file_no] + if addr < key + size: + return symbol, filename, line_no + else: + return None + + def symbolize(self, addr, binary, offset): + if self.binary != binary: + return None + res = self.get_sym_file_line(int(offset, 16)) + if res: + function_name, file_name, line_no = res + result = ['%s in %s %s:%d' % ( + addr, function_name, file_name, line_no)] + print result + return result + else: + return None + + +class SymbolizationLoop(object): + def __init__(self, binary_name_filter=None): + # Used by clients who may want to supply a different binary name. + # E.g. in Chrome several binaries may share a single .dSYM. + self.binary_name_filter = binary_name_filter + self.system = os.uname()[0] + if self.system in ['Linux', 'Darwin']: + self.llvm_symbolizer = LLVMSymbolizerFactory(self.system) + else: + raise Exception('Unknown system') + + def symbolize_address(self, addr, binary, offset): + # Use the chain of symbolizers: + # Breakpad symbolizer -> LLVM symbolizer -> addr2line/atos + # (fall back to next symbolizer if the previous one fails). + if not binary in symbolizers: + symbolizers[binary] = ChainSymbolizer( + [BreakpadSymbolizerFactory(binary), self.llvm_symbolizer]) + result = symbolizers[binary].symbolize(addr, binary, offset) + if result is None: + # Initialize system symbolizer only if other symbolizers failed. + symbolizers[binary].append_symbolizer( + SystemSymbolizerFactory(self.system, addr, binary)) + result = symbolizers[binary].symbolize(addr, binary, offset) + # The system symbolizer must produce some result. + assert result + return result + + def print_symbolized_lines(self, symbolized_lines): + if not symbolized_lines: + print self.current_line + else: + for symbolized_frame in symbolized_lines: + print ' #' + str(self.frame_no) + ' ' + symbolized_frame.rstrip() + self.frame_no += 1 + + def process_stdin(self): + self.frame_no = 0 + for line in sys.stdin: + self.current_line = line.rstrip() + #0 0x7f6e35cf2e45 (/blah/foo.so+0x11fe45) + stack_trace_line_format = ( + '^( *#([0-9]+) *)(0x[0-9a-f]+) *\((.*)\+(0x[0-9a-f]+)\)') + match = re.match(stack_trace_line_format, line) + if not match: + print self.current_line + continue + if DEBUG: + print line + _, frameno_str, addr, binary, offset = match.groups() + if frameno_str == '0': + # Assume that frame #0 is the first frame of new stack trace. + self.frame_no = 0 + original_binary = binary + if self.binary_name_filter: + binary = self.binary_name_filter(binary) + symbolized_line = self.symbolize_address(addr, binary, offset) + if not symbolized_line: + if original_binary != binary: + symbolized_line = self.symbolize_address(addr, binary, offset) + self.print_symbolized_lines(symbolized_line) + + +if __name__ == '__main__': + loop = SymbolizationLoop() + loop.process_stdin() diff --git a/lib/asan/tests/CMakeLists.txt b/lib/asan/tests/CMakeLists.txt index d409d50b995e..272950bc5450 100644 --- a/lib/asan/tests/CMakeLists.txt +++ b/lib/asan/tests/CMakeLists.txt @@ -10,109 +10,178 @@ # instrumentation against the just-built runtime library. include(CheckCXXCompilerFlag) +include(CompilerRTCompile) include_directories(..) include_directories(../..) +set(ASAN_UNITTEST_HEADERS + asan_mac_test.h + asan_test_config.h + asan_test_utils.h) + set(ASAN_UNITTEST_COMMON_CFLAGS + ${COMPILER_RT_GTEST_INCLUDE_CFLAGS} + -I${COMPILER_RT_SOURCE_DIR}/include + -I${COMPILER_RT_SOURCE_DIR}/lib + -I${COMPILER_RT_SOURCE_DIR}/lib/asan -Wall -Wno-format - -fvisibility=hidden + -Werror + -g + -O2 ) -# Support 64-bit and 32-bit builds. -if(LLVM_BUILD_32_BITS) - list(APPEND ASAN_UNITTEST_COMMON_CFLAGS -m32) + +if(SUPPORTS_NO_VARIADIC_MACROS_FLAG) + list(APPEND ASAN_UNITTEST_COMMON_CFLAGS -Wno-variadic-macros) +endif() + +# Use -D instead of definitions to please custom compile command. +if(ANDROID) + list(APPEND ASAN_UNITTEST_COMMON_CFLAGS + -DASAN_LOW_MEMORY=1 + -DASAN_HAS_BLACKLIST=1 + -DASAN_HAS_EXCEPTIONS=1 + -DASAN_NEEDS_SEGV=0 + -DASAN_UAR=0 + -fPIE + ) else() - list(APPEND ASAN_UNITTEST_COMMON_CFLAGS -m64) + list(APPEND ASAN_UNITTEST_COMMON_CFLAGS + -DASAN_HAS_BLACKLIST=1 + -DASAN_HAS_EXCEPTIONS=1 + -DASAN_NEEDS_SEGV=1 + -DASAN_UAR=0 + ) endif() -set(ASAN_GTEST_INCLUDE_CFLAGS - -I${LLVM_MAIN_SRC_DIR}/utils/unittest/googletest/include - -I${LLVM_MAIN_SRC_DIR}/include - -I${LLVM_BINARY_DIR}/include - -D__STDC_CONSTANT_MACROS - -D__STDC_LIMIT_MACROS -) +set(ASAN_LINK_FLAGS) +if(ANDROID) + # On Android, we link with ASan runtime manually + list(APPEND ASAN_LINK_FLAGS -pie) +else() + # On other platforms, we depend on Clang driver behavior, + # passing -fsanitize=address flag. + list(APPEND ASAN_LINK_FLAGS -fsanitize=address) +endif() + +# Unit tests on Mac depend on Foundation. +if(APPLE) + list(APPEND ASAN_LINK_FLAGS -framework Foundation) +endif() +# Unit tests require libstdc++. +list(APPEND ASAN_LINK_FLAGS -lstdc++) + +set(ASAN_BLACKLIST_FILE "${CMAKE_CURRENT_SOURCE_DIR}/asan_test.ignore") set(ASAN_UNITTEST_INSTRUMENTED_CFLAGS ${ASAN_UNITTEST_COMMON_CFLAGS} - ${ASAN_GTEST_INCLUDE_CFLAGS} - -faddress-sanitizer - -O2 - -g - -mllvm "-asan-blacklist=${CMAKE_CURRENT_SOURCE_DIR}/asan_test.ignore" - -DASAN_HAS_BLACKLIST=1 - -DASAN_HAS_EXCEPTIONS=1 - -DASAN_NEEDS_SEGV=1 - -DASAN_UAR=0 + -fsanitize=address + -mllvm "-asan-blacklist=${ASAN_BLACKLIST_FILE}" + -mllvm -asan-stack=1 + -mllvm -asan-globals=1 + -mllvm -asan-mapping-scale=0 # default will be used + -mllvm -asan-mapping-offset-log=-1 # default will be used + -mllvm -asan-use-after-return=0 ) -add_custom_target(AsanTests) -set_target_properties(AsanTests PROPERTIES FOLDER "ASan tests") -function(add_asan_test testname) - add_unittest(AsanTests ${testname} ${ARGN}) - if(LLVM_BUILD_32_BITS) - target_link_libraries(${testname} clang_rt.asan-i386) - else() - target_link_libraries(${testname} clang_rt.asan-x86_64) - endif() - if (APPLE) - # Darwin-specific linker flags. - set_property(TARGET ${testname} APPEND PROPERTY - LINK_FLAGS "-framework Foundation") - elseif (UNIX) - # Linux-specific linker flags. - set_property(TARGET ${testname} APPEND PROPERTY - LINK_FLAGS "-lpthread -ldl -export-dynamic") - endif() - set(add_compile_flags "") - get_property(compile_flags TARGET ${testname} PROPERTY COMPILE_FLAGS) - foreach(arg ${ASAN_UNITTEST_COMMON_CFLAGS}) - set(add_compile_flags "${add_compile_flags} ${arg}") - endforeach(arg ${ASAN_UNITTEST_COMMON_CFLAGS}) - set_property(TARGET ${testname} PROPERTY COMPILE_FLAGS - "${compile_flags} ${add_compile_flags}") -endfunction() - -set(ASAN_NOINST_TEST_SOURCES - asan_noinst_test.cc - asan_break_optimization.cc -) +# Compile source for the given architecture, using compiler +# options in ${ARGN}, and add it to the object list. +macro(asan_compile obj_list source arch) + get_filename_component(basename ${source} NAME) + set(output_obj "${basename}.${arch}.o") + get_target_flags_for_arch(${arch} TARGET_CFLAGS) + clang_compile(${output_obj} ${source} + CFLAGS ${ARGN} ${TARGET_CFLAGS} + DEPS gtest ${ASAN_RUNTIME_LIBRARIES} + ${ASAN_UNITTEST_HEADERS} + ${ASAN_BLACKLIST_FILE}) + list(APPEND ${obj_list} ${output_obj}) +endmacro() -set(ASAN_INST_TEST_OBJECTS) - -# We only support building instrumented tests when we're not cross compiling -# and targeting a unix-like system where we can predict viable compilation and -# linking strategies. -if("${CMAKE_HOST_SYSTEM}" STREQUAL "${CMAKE_SYSTEM}" AND UNIX) - - # This function is a custom routine to manage manually compiling source files - # for unit tests with the just-built Clang binary, using the ASan - # instrumentation, and linking them into a test executable. - function(add_asan_compile_command source extra_cflags) - set(output_obj "${source}.asan.o") - add_custom_command( - OUTPUT ${output_obj} - COMMAND clang - ${ASAN_UNITTEST_INSTRUMENTED_CFLAGS} - ${extra_cflags} - -c -o "${output_obj}" - ${CMAKE_CURRENT_SOURCE_DIR}/${source} - MAIN_DEPENDENCY ${source} - DEPENDS clang clang_rt.asan-i386 clang_rt.asan-x86_64 ${ARGN} - ) - endfunction() - - add_asan_compile_command(asan_globals_test.cc "") - add_asan_compile_command(asan_test.cc "") - list(APPEND ASAN_INST_TEST_OBJECTS asan_globals_test.cc.asan.o - asan_test.cc.asan.o) +# Link ASan unit test for a given architecture from a set +# of objects in ${ARGN}. +macro(add_asan_test test_suite test_name arch) + get_target_flags_for_arch(${arch} TARGET_LINK_FLAGS) + add_compiler_rt_test(${test_suite} ${test_name} + OBJECTS ${ARGN} + DEPS ${ASAN_RUNTIME_LIBRARIES} ${ARGN} + LINK_FLAGS ${ASAN_LINK_FLAGS} + ${TARGET_LINK_FLAGS}) +endmacro() + +# Main AddressSanitizer unit tests. +add_custom_target(AsanUnitTests) +set_target_properties(AsanUnitTests PROPERTIES FOLDER "ASan unit tests") +# ASan benchmarks (not actively used now). +add_custom_target(AsanBenchmarks) +set_target_properties(AsanBenchmarks PROPERTIES FOLDER "Asan benchmarks") + +# Adds ASan unit tests and benchmarks for architecture. +macro(add_asan_tests_for_arch arch) + # Build gtest instrumented with ASan. + set(ASAN_INST_GTEST) + asan_compile(ASAN_INST_GTEST ${COMPILER_RT_GTEST_SOURCE} ${arch} + ${ASAN_UNITTEST_INSTRUMENTED_CFLAGS}) + # Instrumented tests. + set(ASAN_INST_TEST_OBJECTS) + asan_compile(ASAN_INST_TEST_OBJECTS asan_globals_test.cc ${arch} + ${ASAN_UNITTEST_INSTRUMENTED_CFLAGS}) + asan_compile(ASAN_INST_TEST_OBJECTS asan_test.cc ${arch} + ${ASAN_UNITTEST_INSTRUMENTED_CFLAGS}) if (APPLE) - add_asan_compile_command(asan_mac_test.mm "-ObjC") - list(APPEND ASAN_INST_TEST_OBJECTS asan_mac_test.mm.asan.o) + asan_compile(ASAN_INST_TEST_OBJECTS asan_mac_test.mm ${arch} + ${ASAN_UNITTEST_INSTRUMENTED_CFLAGS} -ObjC) endif() + # Uninstrumented tests. + set(ASAN_NOINST_TEST_OBJECTS) + asan_compile(ASAN_NOINST_TEST_OBJECTS asan_noinst_test.cc ${arch} + ${ASAN_UNITTEST_COMMON_CFLAGS}) + asan_compile(ASAN_NOINST_TEST_OBJECTS asan_test_main.cc ${arch} + ${ASAN_UNITTEST_COMMON_CFLAGS}) + # Link everything together. + add_asan_test(AsanUnitTests "Asan-${arch}-Test" ${arch} + ${ASAN_NOINST_TEST_OBJECTS} + ${ASAN_INST_TEST_OBJECTS} ${ASAN_INST_GTEST}) + + # Instrumented benchmarks. + set(ASAN_BENCHMARKS_OBJECTS) + asan_compile(ASAN_BENCHMARKS_OBJECTS asan_benchmarks_test.cc ${arch} + ${ASAN_UNITTEST_INSTRUMENTED_CFLAGS}) + # Link benchmarks. + add_asan_test(AsanBenchmarks "Asan-${arch}-Benchmark" ${arch} + ${ASAN_BENCHMARKS_OBJECTS} ${ASAN_INST_GTEST}) +endmacro() +if(COMPILER_RT_CAN_EXECUTE_TESTS) + if(CAN_TARGET_x86_64) + add_asan_tests_for_arch(x86_64) + endif() + if(CAN_TARGET_i386) + add_asan_tests_for_arch(i386) + endif() endif() -add_asan_test(AsanTest ${ASAN_NOINST_TEST_SOURCES} - ${ASAN_INST_TEST_OBJECTS}) +if(ANDROID) + # We assume that unit tests on Android are built in a build + # tree with fresh Clang as a host compiler. + set(ASAN_NOINST_TEST_SOURCES asan_noinst_test.cc asan_test_main.cc) + set(ASAN_INST_TEST_SOURCES asan_globals_test.cc asan_test.cc) + add_library(asan_noinst_test OBJECT ${ASAN_NOINST_TEST_SOURCES}) + set_target_compile_flags(asan_noinst_test ${ASAN_UNITTEST_COMMON_CFLAGS}) + add_library(asan_inst_test OBJECT + ${ASAN_INST_TEST_SOURCES} ${COMPILER_RT_GTEST_SOURCE}) + set_target_compile_flags(asan_inst_test ${ASAN_UNITTEST_INSTRUMENTED_CFLAGS}) + add_executable(AsanTest + $<TARGET_OBJECTS:asan_noinst_test> + $<TARGET_OBJECTS:asan_inst_test> + ) + # Setup correct output directory and link flags. + get_unittest_directory(OUTPUT_DIR) + set_target_properties(AsanTest PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${OUTPUT_DIR}) + set_target_link_flags(AsanTest ${ASAN_LINK_FLAGS}) + target_link_libraries(AsanTest clang_rt.asan-arm-android) + # Add unit test to test suite. + add_dependencies(AsanUnitTests AsanTest) +endif() diff --git a/lib/asan/tests/asan_benchmarks_test.cc b/lib/asan/tests/asan_benchmarks_test.cc index a142fd23e1b8..fc522de475fa 100644 --- a/lib/asan/tests/asan_benchmarks_test.cc +++ b/lib/asan/tests/asan_benchmarks_test.cc @@ -12,7 +12,6 @@ // Some benchmarks for the instrumented code. //===----------------------------------------------------------------------===// -#include "asan_test_config.h" #include "asan_test_utils.h" template<class T> diff --git a/lib/asan/tests/asan_globals_test.cc b/lib/asan/tests/asan_globals_test.cc index 6467524ca32c..dc2e9bbb0530 100644 --- a/lib/asan/tests/asan_globals_test.cc +++ b/lib/asan/tests/asan_globals_test.cc @@ -1,4 +1,4 @@ -//===-- asan_globals_test.cc ----------------------===// +//===-- asan_globals_test.cc ----------------------------------------------===// // // The LLVM Compiler Infrastructure // diff --git a/lib/asan/tests/asan_mac_test.mm b/lib/asan/tests/asan_mac_test.mm index 4e5873b74485..4cbd2bb247fd 100644 --- a/lib/asan/tests/asan_mac_test.mm +++ b/lib/asan/tests/asan_mac_test.mm @@ -57,7 +57,7 @@ char kStartupStr[] = @implementation LoadSomething +(void) load { - for (int i = 0; i < strlen(kStartupStr); i++) { + for (size_t i = 0; i < strlen(kStartupStr); i++) { access_memory(&kStartupStr[i]); // make sure no optimizations occur. } // Don't print anything here not to interfere with the death tests. @@ -66,13 +66,13 @@ char kStartupStr[] = @end void worker_do_alloc(int size) { - char * volatile mem = malloc(size); + char * volatile mem = (char * volatile)malloc(size); mem[0] = 0; // Ok free(mem); } void worker_do_crash(int size) { - char * volatile mem = malloc(size); + char * volatile mem = (char * volatile)malloc(size); access_memory(&mem[size]); // BOOM free(mem); } @@ -167,7 +167,7 @@ void TestGCDSourceEvent() { dispatch_time(DISPATCH_TIME_NOW, 1LL * NSEC_PER_SEC); dispatch_source_set_timer(timer, milestone, DISPATCH_TIME_FOREVER, 0); - char * volatile mem = malloc(10); + char * volatile mem = (char * volatile)malloc(10); dispatch_source_set_event_handler(timer, ^{ access_memory(&mem[10]); }); @@ -184,7 +184,7 @@ void TestGCDSourceCancel() { dispatch_time(DISPATCH_TIME_NOW, 1LL * NSEC_PER_SEC); dispatch_source_set_timer(timer, milestone, DISPATCH_TIME_FOREVER, 0); - char * volatile mem = malloc(10); + char * volatile mem = (char * volatile)malloc(10); // Both dispatch_source_set_cancel_handler() and // dispatch_source_set_event_handler() use dispatch_barrier_async_f(). // It's tricky to test dispatch_source_set_cancel_handler() separately, @@ -202,7 +202,7 @@ void TestGCDSourceCancel() { void TestGCDGroupAsync() { dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_group_t group = dispatch_group_create(); - char * volatile mem = malloc(10); + char * volatile mem = (char * volatile)malloc(10); dispatch_group_async(group, queue, ^{ access_memory(&mem[10]); }); diff --git a/lib/asan/tests/asan_noinst_test.cc b/lib/asan/tests/asan_noinst_test.cc index 44d4c3c845b2..576312bf319f 100644 --- a/lib/asan/tests/asan_noinst_test.cc +++ b/lib/asan/tests/asan_noinst_test.cc @@ -1,4 +1,4 @@ -//===-- asan_noinst_test.cc ----------------------===// +//===-- asan_noinst_test.cc -----------------------------------------------===// // // The LLVM Compiler Infrastructure // @@ -11,12 +11,13 @@ // // This test file should be compiled w/o asan instrumentation. //===----------------------------------------------------------------------===// + #include "asan_allocator.h" -#include "asan_interface.h" #include "asan_internal.h" #include "asan_mapping.h" #include "asan_stack.h" #include "asan_test_utils.h" +#include "sanitizer/asan_interface.h" #include <assert.h> #include <stdio.h> @@ -24,15 +25,6 @@ #include <string.h> // for memset() #include <algorithm> #include <vector> -#include "gtest/gtest.h" - -// Simple stand-alone pseudorandom number generator. -// Current algorithm is ANSI C linear congruential PRNG. -static inline u32 my_rand(u32* state) { - return (*state = *state * 1103515245 + 12345) >> 16; -} - -static u32 global_seed = 0; TEST(AddressSanitizer, InternalSimpleDeathTest) { @@ -40,18 +32,18 @@ TEST(AddressSanitizer, InternalSimpleDeathTest) { } static void MallocStress(size_t n) { - u32 seed = my_rand(&global_seed); - __asan::AsanStackTrace stack1; + u32 seed = my_rand(); + __asan::StackTrace stack1; stack1.trace[0] = 0xa123; stack1.trace[1] = 0xa456; stack1.size = 2; - __asan::AsanStackTrace stack2; + __asan::StackTrace stack2; stack2.trace[0] = 0xb123; stack2.trace[1] = 0xb456; stack2.size = 2; - __asan::AsanStackTrace stack3; + __asan::StackTrace stack3; stack3.trace[0] = 0xc123; stack3.trace[1] = 0xc456; stack3.size = 2; @@ -60,20 +52,21 @@ static void MallocStress(size_t n) { for (size_t i = 0; i < n; i++) { if ((i % 3) == 0) { if (vec.empty()) continue; - size_t idx = my_rand(&seed) % vec.size(); + size_t idx = my_rand_r(&seed) % vec.size(); void *ptr = vec[idx]; vec[idx] = vec.back(); vec.pop_back(); - __asan::asan_free(ptr, &stack1); + __asan::asan_free(ptr, &stack1, __asan::FROM_MALLOC); } else { - size_t size = my_rand(&seed) % 1000 + 1; - switch ((my_rand(&seed) % 128)) { + size_t size = my_rand_r(&seed) % 1000 + 1; + switch ((my_rand_r(&seed) % 128)) { case 0: size += 1024; break; case 1: size += 2048; break; case 2: size += 4096; break; } - size_t alignment = 1 << (my_rand(&seed) % 10 + 1); - char *ptr = (char*)__asan::asan_memalign(alignment, size, &stack2); + size_t alignment = 1 << (my_rand_r(&seed) % 10 + 1); + char *ptr = (char*)__asan::asan_memalign(alignment, size, + &stack2, __asan::FROM_MALLOC); vec.push_back(ptr); ptr[0] = 0; ptr[size-1] = 0; @@ -81,7 +74,7 @@ static void MallocStress(size_t n) { } } for (size_t i = 0; i < vec.size(); i++) - __asan::asan_free(vec[i], &stack3); + __asan::asan_free(vec[i], &stack3, __asan::FROM_MALLOC); } @@ -118,7 +111,7 @@ TEST(AddressSanitizer, DISABLED_InternalPrintShadow) { } static uptr pc_array[] = { -#if __WORDSIZE == 64 +#if SANITIZER_WORDSIZE == 64 0x7effbf756068ULL, 0x7effbf75e5abULL, 0x7effc0625b7cULL, @@ -164,7 +157,7 @@ static uptr pc_array[] = { 0x7effbcc3e726ULL, 0x7effbcc40852ULL, 0x7effb681ec4dULL, -#endif // __WORDSIZE +#endif // SANITIZER_WORDSIZE 0xB0B5E768, 0x7B682EC1, 0x367F9918, @@ -208,22 +201,22 @@ static uptr pc_array[] = { }; void CompressStackTraceTest(size_t n_iter) { - u32 seed = my_rand(&global_seed); - const size_t kNumPcs = ASAN_ARRAY_SIZE(pc_array); + u32 seed = my_rand(); + const size_t kNumPcs = ARRAY_SIZE(pc_array); u32 compressed[2 * kNumPcs]; for (size_t iter = 0; iter < n_iter; iter++) { std::random_shuffle(pc_array, pc_array + kNumPcs); - __asan::AsanStackTrace stack0, stack1; + __asan::StackTrace stack0, stack1; stack0.CopyFrom(pc_array, kNumPcs); - stack0.size = std::max((size_t)1, (size_t)(my_rand(&seed) % stack0.size)); + stack0.size = std::max((size_t)1, (size_t)(my_rand_r(&seed) % stack0.size)); size_t compress_size = - std::max((size_t)2, (size_t)my_rand(&seed) % (2 * kNumPcs)); + std::max((size_t)2, (size_t)my_rand_r(&seed) % (2 * kNumPcs)); size_t n_frames = - __asan::AsanStackTrace::CompressStack(&stack0, compressed, compress_size); + __asan::StackTrace::CompressStack(&stack0, compressed, compress_size); Ident(n_frames); assert(n_frames <= stack0.size); - __asan::AsanStackTrace::UncompressStack(&stack1, compressed, compress_size); + __asan::StackTrace::UncompressStack(&stack1, compressed, compress_size); assert(stack1.size == n_frames); for (size_t i = 0; i < stack1.size; i++) { assert(stack0.trace[i] == stack1.trace[i]); @@ -236,17 +229,17 @@ TEST(AddressSanitizer, CompressStackTraceTest) { } void CompressStackTraceBenchmark(size_t n_iter) { - const size_t kNumPcs = ASAN_ARRAY_SIZE(pc_array); + const size_t kNumPcs = ARRAY_SIZE(pc_array); u32 compressed[2 * kNumPcs]; std::random_shuffle(pc_array, pc_array + kNumPcs); - __asan::AsanStackTrace stack0; + __asan::StackTrace stack0; stack0.CopyFrom(pc_array, kNumPcs); stack0.size = kNumPcs; for (size_t iter = 0; iter < n_iter; iter++) { size_t compress_size = kNumPcs; size_t n_frames = - __asan::AsanStackTrace::CompressStack(&stack0, compressed, compress_size); + __asan::StackTrace::CompressStack(&stack0, compressed, compress_size); Ident(n_frames); } } @@ -256,18 +249,18 @@ TEST(AddressSanitizer, CompressStackTraceBenchmark) { } TEST(AddressSanitizer, QuarantineTest) { - __asan::AsanStackTrace stack; + __asan::StackTrace stack; stack.trace[0] = 0x890; stack.size = 1; const int size = 32; void *p = __asan::asan_malloc(size, &stack); - __asan::asan_free(p, &stack); + __asan::asan_free(p, &stack, __asan::FROM_MALLOC); size_t i; size_t max_i = 1 << 30; for (i = 0; i < max_i; i++) { void *p1 = __asan::asan_malloc(size, &stack); - __asan::asan_free(p1, &stack); + __asan::asan_free(p1, &stack, __asan::FROM_MALLOC); if (p1 == p) break; } // fprintf(stderr, "i=%ld\n", i); @@ -277,14 +270,14 @@ TEST(AddressSanitizer, QuarantineTest) { void *ThreadedQuarantineTestWorker(void *unused) { (void)unused; - u32 seed = my_rand(&global_seed); - __asan::AsanStackTrace stack; + u32 seed = my_rand(); + __asan::StackTrace stack; stack.trace[0] = 0x890; stack.size = 1; for (size_t i = 0; i < 1000; i++) { - void *p = __asan::asan_malloc(1 + (my_rand(&seed) % 4000), &stack); - __asan::asan_free(p, &stack); + void *p = __asan::asan_malloc(1 + (my_rand_r(&seed) % 4000), &stack); + __asan::asan_free(p, &stack, __asan::FROM_MALLOC); } return NULL; } @@ -296,8 +289,8 @@ TEST(AddressSanitizer, ThreadedQuarantineTest) { size_t mmaped1 = __asan_get_heap_size(); for (int i = 0; i < n_threads; i++) { pthread_t t; - pthread_create(&t, NULL, ThreadedQuarantineTestWorker, 0); - pthread_join(t, 0); + PTHREAD_CREATE(&t, NULL, ThreadedQuarantineTestWorker, 0); + PTHREAD_JOIN(t, 0); size_t mmaped2 = __asan_get_heap_size(); EXPECT_LT(mmaped2 - mmaped1, 320U * (1 << 20)); } @@ -305,7 +298,7 @@ TEST(AddressSanitizer, ThreadedQuarantineTest) { void *ThreadedOneSizeMallocStress(void *unused) { (void)unused; - __asan::AsanStackTrace stack; + __asan::StackTrace stack; stack.trace[0] = 0x890; stack.size = 1; const size_t kNumMallocs = 1000; @@ -315,7 +308,7 @@ void *ThreadedOneSizeMallocStress(void *unused) { p[i] = __asan::asan_malloc(32, &stack); } for (size_t i = 0; i < kNumMallocs; i++) { - __asan::asan_free(p[i], &stack); + __asan::asan_free(p[i], &stack, __asan::FROM_MALLOC); } } return NULL; @@ -325,10 +318,10 @@ TEST(AddressSanitizer, ThreadedOneSizeMallocStressTest) { const int kNumThreads = 4; pthread_t t[kNumThreads]; for (int i = 0; i < kNumThreads; i++) { - pthread_create(&t[i], 0, ThreadedOneSizeMallocStress, 0); + PTHREAD_CREATE(&t[i], 0, ThreadedOneSizeMallocStress, 0); } for (int i = 0; i < kNumThreads; i++) { - pthread_join(t[i], 0); + PTHREAD_JOIN(t[i], 0); } } @@ -336,16 +329,20 @@ TEST(AddressSanitizer, MemsetWildAddressTest) { typedef void*(*memset_p)(void*, int, size_t); // Prevent inlining of memset(). volatile memset_p libc_memset = (memset_p)memset; - EXPECT_DEATH(libc_memset((void*)(kLowShadowBeg + kPageSize), 0, 100), + EXPECT_DEATH(libc_memset((void*)(kLowShadowBeg + 200), 0, 100), "unknown-crash.*low shadow"); - EXPECT_DEATH(libc_memset((void*)(kShadowGapBeg + kPageSize), 0, 100), + EXPECT_DEATH(libc_memset((void*)(kShadowGapBeg + 200), 0, 100), "unknown-crash.*shadow gap"); - EXPECT_DEATH(libc_memset((void*)(kHighShadowBeg + kPageSize), 0, 100), + EXPECT_DEATH(libc_memset((void*)(kHighShadowBeg + 200), 0, 100), "unknown-crash.*high shadow"); } TEST(AddressSanitizerInterface, GetEstimatedAllocatedSize) { +#if ASAN_ALLOCATOR_VERSION == 1 EXPECT_EQ(1U, __asan_get_estimated_allocated_size(0)); +#elif ASAN_ALLOCATOR_VERSION == 2 + EXPECT_EQ(0U, __asan_get_estimated_allocated_size(0)); +#endif const size_t sizes[] = { 1, 30, 1<<30 }; for (size_t i = 0; i < 3; i++) { EXPECT_EQ(sizes[i], __asan_get_estimated_allocated_size(sizes[i])); @@ -370,23 +367,32 @@ TEST(AddressSanitizerInterface, GetAllocatedSizeAndOwnershipTest) { // We cannot call GetAllocatedSize from the memory we didn't map, // and from the interior pointers (not returned by previous malloc). void *wild_addr = (void*)0x1; - EXPECT_EQ(false, __asan_get_ownership(wild_addr)); + EXPECT_FALSE(__asan_get_ownership(wild_addr)); EXPECT_DEATH(__asan_get_allocated_size(wild_addr), kGetAllocatedSizeErrorMsg); - EXPECT_EQ(false, __asan_get_ownership(array + kArraySize / 2)); + EXPECT_FALSE(__asan_get_ownership(array + kArraySize / 2)); EXPECT_DEATH(__asan_get_allocated_size(array + kArraySize / 2), kGetAllocatedSizeErrorMsg); // NULL is not owned, but is a valid argument for __asan_get_allocated_size(). - EXPECT_EQ(false, __asan_get_ownership(NULL)); + EXPECT_FALSE(__asan_get_ownership(NULL)); EXPECT_EQ(0U, __asan_get_allocated_size(NULL)); // When memory is freed, it's not owned, and call to GetAllocatedSize // is forbidden. free(array); - EXPECT_EQ(false, __asan_get_ownership(array)); + EXPECT_FALSE(__asan_get_ownership(array)); EXPECT_DEATH(__asan_get_allocated_size(array), kGetAllocatedSizeErrorMsg); - delete int_ptr; + + void *zero_alloc = Ident(malloc(0)); + if (zero_alloc != 0) { + // If malloc(0) is not null, this pointer is owned and should have valid + // allocated size. + EXPECT_TRUE(__asan_get_ownership(zero_alloc)); + // Allocated size is 0 or 1 depending on the allocator used. + EXPECT_LT(__asan_get_allocated_size(zero_alloc), 2U); + } + free(zero_alloc); } TEST(AddressSanitizerInterface, GetCurrentAllocatedBytesTest) { @@ -410,6 +416,7 @@ static void DoDoubleFree() { delete Ident(x); } +#if ASAN_ALLOCATOR_VERSION == 1 // This test is run in a separate process, so that large malloced // chunk won't remain in the free lists after the test. // Note: use ASSERT_* instead of EXPECT_* here. @@ -441,9 +448,26 @@ static void RunGetHeapSizeTestAndDie() { TEST(AddressSanitizerInterface, GetHeapSizeTest) { EXPECT_DEATH(RunGetHeapSizeTestAndDie(), "double-free"); } +#elif ASAN_ALLOCATOR_VERSION == 2 +TEST(AddressSanitizerInterface, GetHeapSizeTest) { + // asan_allocator2 does not keep huge chunks in free list, but unmaps them. + // The chunk should be greater than the quarantine size, + // otherwise it will be stuck in quarantine instead of being unmaped. + static const size_t kLargeMallocSize = 1 << 29; // 512M + uptr old_heap_size = __asan_get_heap_size(); + for (int i = 0; i < 3; i++) { + // fprintf(stderr, "allocating %zu bytes:\n", kLargeMallocSize); + free(Ident(malloc(kLargeMallocSize))); + EXPECT_EQ(old_heap_size, __asan_get_heap_size()); + } +} +#endif // Note: use ASSERT_* instead of EXPECT_* here. static void DoLargeMallocForGetFreeBytesTestAndDie() { +#if ASAN_ALLOCATOR_VERSION == 1 + // asan_allocator2 does not keep large chunks in free_lists, so this test + // will not work. size_t old_free_bytes, new_free_bytes; static const size_t kLargeMallocSize = 1 << 29; // 512M // If we malloc and free a large memory chunk, it will not fall @@ -455,33 +479,42 @@ static void DoLargeMallocForGetFreeBytesTestAndDie() { new_free_bytes = __asan_get_free_bytes(); fprintf(stderr, "free bytes after malloc and free: %zu\n", new_free_bytes); ASSERT_GE(new_free_bytes, old_free_bytes + kLargeMallocSize); +#endif // ASAN_ALLOCATOR_VERSION // Test passed. DoDoubleFree(); } TEST(AddressSanitizerInterface, GetFreeBytesTest) { - static const size_t kNumOfChunks = 100; - static const size_t kChunkSize = 100; - char *chunks[kNumOfChunks]; - size_t i; - size_t old_free_bytes, new_free_bytes; +#if ASAN_ALLOCATOR_VERSION == 1 // Allocate a small chunk. Now allocator probably has a lot of these // chunks to fulfill future requests. So, future requests will decrease - // the number of free bytes. - chunks[0] = Ident((char*)malloc(kChunkSize)); - old_free_bytes = __asan_get_free_bytes(); - for (i = 1; i < kNumOfChunks; i++) { - chunks[i] = Ident((char*)malloc(kChunkSize)); - new_free_bytes = __asan_get_free_bytes(); - EXPECT_LT(new_free_bytes, old_free_bytes); - old_free_bytes = new_free_bytes; + // the number of free bytes. Do this only on systems where there + // is enough memory for such assumptions. + if (SANITIZER_WORDSIZE == 64 && !ASAN_LOW_MEMORY) { + static const size_t kNumOfChunks = 100; + static const size_t kChunkSize = 100; + char *chunks[kNumOfChunks]; + size_t i; + size_t old_free_bytes, new_free_bytes; + chunks[0] = Ident((char*)malloc(kChunkSize)); + old_free_bytes = __asan_get_free_bytes(); + for (i = 1; i < kNumOfChunks; i++) { + chunks[i] = Ident((char*)malloc(kChunkSize)); + new_free_bytes = __asan_get_free_bytes(); + EXPECT_LT(new_free_bytes, old_free_bytes); + old_free_bytes = new_free_bytes; + } + for (i = 0; i < kNumOfChunks; i++) + free(chunks[i]); } +#endif EXPECT_DEATH(DoLargeMallocForGetFreeBytesTestAndDie(), "double-free"); } -static const size_t kManyThreadsMallocSizes[] = {5, 1UL<<10, 1UL<<20, 357}; +static const size_t kManyThreadsMallocSizes[] = {5, 1UL<<10, 1UL<<14, 357}; static const size_t kManyThreadsIterations = 250; -static const size_t kManyThreadsNumThreads = (__WORDSIZE == 32) ? 40 : 200; +static const size_t kManyThreadsNumThreads = + (SANITIZER_WORDSIZE == 32) ? 40 : 200; void *ManyThreadsWithStatsWorker(void *arg) { (void)arg; @@ -490,6 +523,8 @@ void *ManyThreadsWithStatsWorker(void *arg) { free(Ident(malloc(kManyThreadsMallocSizes[size_index]))); } } + // Just one large allocation. + free(Ident(malloc(1 << 20))); return 0; } @@ -498,11 +533,11 @@ TEST(AddressSanitizerInterface, ManyThreadsWithStatsStressTest) { pthread_t threads[kManyThreadsNumThreads]; before_test = __asan_get_current_allocated_bytes(); for (i = 0; i < kManyThreadsNumThreads; i++) { - pthread_create(&threads[i], 0, + PTHREAD_CREATE(&threads[i], 0, (void* (*)(void *x))ManyThreadsWithStatsWorker, (void*)i); } for (i = 0; i < kManyThreadsNumThreads; i++) { - pthread_join(threads[i], 0); + PTHREAD_JOIN(threads[i], 0); } after_test = __asan_get_current_allocated_bytes(); // ASan stats also reflect memory usage of internal ASan RTL structs, @@ -649,6 +684,45 @@ TEST(AddressSanitizerInterface, PoisoningStressTest) { } } +TEST(AddressSanitizerInterface, PoisonedRegion) { + size_t rz = 16; + for (size_t size = 1; size <= 64; size++) { + char *p = new char[size]; + uptr x = reinterpret_cast<uptr>(p); + for (size_t beg = 0; beg < size + rz; beg++) { + for (size_t end = beg; end < size + rz; end++) { + uptr first_poisoned = __asan_region_is_poisoned(x + beg, end - beg); + if (beg == end) { + EXPECT_FALSE(first_poisoned); + } else if (beg < size && end <= size) { + EXPECT_FALSE(first_poisoned); + } else if (beg >= size) { + EXPECT_EQ(x + beg, first_poisoned); + } else { + EXPECT_GT(end, size); + EXPECT_EQ(x + size, first_poisoned); + } + } + } + delete [] p; + } +} + +// This is a performance benchmark for manual runs. +// asan's memset interceptor calls mem_is_zero for the entire shadow region. +// the profile should look like this: +// 89.10% [.] __memset_sse2 +// 10.50% [.] __sanitizer::mem_is_zero +// I.e. mem_is_zero should consume ~ SHADOW_GRANULARITY less CPU cycles +// than memset itself. +TEST(AddressSanitizerInterface, DISABLED_Stress_memset) { + size_t size = 1 << 20; + char *x = new char[size]; + for (int i = 0; i < 100000; i++) + Ident(memset)(x, 0, size); + delete [] x; +} + static const char *kInvalidPoisonMessage = "invalid-poison-memory-range"; static const char *kInvalidUnpoisonMessage = "invalid-unpoison-memory-range"; @@ -670,20 +744,29 @@ TEST(AddressSanitizerInterface, DISABLED_InvalidPoisonAndUnpoisonCallsTest) { } static void ErrorReportCallbackOneToZ(const char *report) { - write(2, "ABCDEF", 6); + int report_len = strlen(report); + ASSERT_EQ(6, write(2, "ABCDEF", 6)); + ASSERT_EQ(report_len, write(2, report, report_len)); + ASSERT_EQ(6, write(2, "ABCDEF", 6)); + _exit(1); } TEST(AddressSanitizerInterface, SetErrorReportCallbackTest) { __asan_set_error_report_callback(ErrorReportCallbackOneToZ); - EXPECT_DEATH(__asan_report_error(0, 0, 0, 0, true, 1), "ABCDEF"); + EXPECT_DEATH(__asan_report_error(0, 0, 0, 0, true, 1), + ASAN_PCRE_DOTALL "ABCDEF.*AddressSanitizer.*WRITE.*ABCDEF"); __asan_set_error_report_callback(NULL); } TEST(AddressSanitizerInterface, GetOwnershipStressTest) { std::vector<char *> pointers; std::vector<size_t> sizes; +#if ASAN_ALLOCATOR_VERSION == 1 const size_t kNumMallocs = - (__WORDSIZE <= 32 || ASAN_LOW_MEMORY) ? 1 << 10 : 1 << 14; + (SANITIZER_WORDSIZE <= 32 || ASAN_LOW_MEMORY) ? 1 << 10 : 1 << 14; +#elif ASAN_ALLOCATOR_VERSION == 2 // too slow with asan_allocator2. :( + const size_t kNumMallocs = 1 << 9; +#endif for (size_t i = 0; i < kNumMallocs; i++) { size_t size = i * 100 + 1; pointers.push_back((char*)malloc(size)); diff --git a/lib/asan/tests/asan_test.cc b/lib/asan/tests/asan_test.cc index 8e967e929899..5fa65b2af5dc 100644 --- a/lib/asan/tests/asan_test.cc +++ b/lib/asan/tests/asan_test.cc @@ -1,4 +1,4 @@ -//===-- asan_test.cc ----------------------===// +//===-- asan_test.cc ------------------------------------------------------===// // // The LLVM Compiler Infrastructure // @@ -19,17 +19,26 @@ #include <stdint.h> #include <setjmp.h> #include <assert.h> +#include <algorithm> + +#ifdef __linux__ +# include <sys/prctl.h> +# include <sys/types.h> +# include <sys/stat.h> +# include <fcntl.h> +#include <unistd.h> +#endif #if defined(__i386__) || defined(__x86_64__) #include <emmintrin.h> #endif -#include "asan_test_config.h" #include "asan_test_utils.h" #ifndef __APPLE__ #include <malloc.h> #else +#include <malloc/malloc.h> #include <AvailabilityMacros.h> // For MAC_OS_X_VERSION_* #include <CoreFoundation/CFString.h> #endif // __APPLE__ @@ -47,17 +56,8 @@ typedef uint16_t U2; typedef uint32_t U4; typedef uint64_t U8; -static const char *progname; static const int kPageSize = 4096; -// Simple stand-alone pseudorandom number generator. -// Current algorithm is ANSI C linear congruential PRNG. -static inline uint32_t my_rand(uint32_t* state) { - return (*state = *state * 1103515245 + 12345) >> 16; -} - -static uint32_t global_seed = 0; - const size_t kLargeMalloc = 1 << 24; template<typename T> @@ -66,7 +66,7 @@ NOINLINE void asan_write(T *a) { } NOINLINE void asan_write_sized_aligned(uint8_t *p, size_t size) { - EXPECT_EQ(0, ((uintptr_t)p % size)); + EXPECT_EQ(0U, ((uintptr_t)p % size)); if (size == 1) asan_write((uint8_t*)p); else if (size == 2) asan_write((uint16_t*)p); else if (size == 4) asan_write((uint32_t*)p); @@ -130,6 +130,8 @@ NOINLINE void uaf_test(int size, int off) { TEST(AddressSanitizer, HasFeatureAddressSanitizerTest) { #if defined(__has_feature) && __has_feature(address_sanitizer) bool asan = 1; +#elif defined(__SANITIZE_ADDRESS__) + bool asan = 1; #else bool asan = 0; #endif @@ -141,29 +143,24 @@ TEST(AddressSanitizer, SimpleDeathTest) { } TEST(AddressSanitizer, VariousMallocsTest) { - // fprintf(stderr, "malloc:\n"); int *a = (int*)malloc(100 * sizeof(int)); a[50] = 0; free(a); - // fprintf(stderr, "realloc:\n"); int *r = (int*)malloc(10); r = (int*)realloc(r, 2000 * sizeof(int)); r[1000] = 0; free(r); - // fprintf(stderr, "operator new []\n"); int *b = new int[100]; b[50] = 0; delete [] b; - // fprintf(stderr, "operator new\n"); int *c = new int; *c = 0; delete c; -#if !defined(__APPLE__) && !defined(ANDROID) - // fprintf(stderr, "posix_memalign\n"); +#if !defined(__APPLE__) && !defined(ANDROID) && !defined(__ANDROID__) int *pm; int pm_res = posix_memalign((void**)&pm, kPageSize, kPageSize); EXPECT_EQ(0, pm_res); @@ -172,7 +169,7 @@ TEST(AddressSanitizer, VariousMallocsTest) { #if !defined(__APPLE__) int *ma = (int*)memalign(kPageSize, kPageSize); - EXPECT_EQ(0, (uintptr_t)ma % kPageSize); + EXPECT_EQ(0U, (uintptr_t)ma % kPageSize); ma[123] = 0; free(ma); #endif // __APPLE__ @@ -186,19 +183,19 @@ TEST(AddressSanitizer, CallocTest) { TEST(AddressSanitizer, VallocTest) { void *a = valloc(100); - EXPECT_EQ(0, (uintptr_t)a % kPageSize); + EXPECT_EQ(0U, (uintptr_t)a % kPageSize); free(a); } #ifndef __APPLE__ TEST(AddressSanitizer, PvallocTest) { char *a = (char*)pvalloc(kPageSize + 100); - EXPECT_EQ(0, (uintptr_t)a % kPageSize); + EXPECT_EQ(0U, (uintptr_t)a % kPageSize); a[kPageSize + 101] = 1; // we should not report an error here. free(a); a = (char*)pvalloc(0); // pvalloc(0) should allocate at least one page. - EXPECT_EQ(0, (uintptr_t)a % kPageSize); + EXPECT_EQ(0U, (uintptr_t)a % kPageSize); a[101] = 1; // we should not report an error here. free(a); } @@ -214,8 +211,8 @@ void *TSDWorker(void *test_key) { void TSDDestructor(void *tsd) { // Spawning a thread will check that the current thread id is not -1. pthread_t th; - pthread_create(&th, NULL, TSDWorker, NULL); - pthread_join(th, NULL); + PTHREAD_CREATE(&th, NULL, TSDWorker, NULL); + PTHREAD_JOIN(th, NULL); } // This tests triggers the thread-specific data destruction fiasco which occurs @@ -229,8 +226,8 @@ TEST(AddressSanitizer, DISABLED_TSDTest) { pthread_t th; pthread_key_t test_key; pthread_key_create(&test_key, TSDDestructor); - pthread_create(&th, NULL, TSDWorker, &test_key); - pthread_join(th, NULL); + PTHREAD_CREATE(&th, NULL, TSDWorker, &test_key); + PTHREAD_JOIN(th, NULL); pthread_key_delete(test_key); } @@ -245,10 +242,10 @@ void OOBTest() { EXPECT_DEATH(oob_test<T>(size, i), expected_str); } - for (int i = 0; i < size - sizeof(T) + 1; i++) + for (int i = 0; i < (int)(size - sizeof(T) + 1); i++) oob_test<T>(size, i); - for (int i = size - sizeof(T) + 1; i <= size + 3 * sizeof(T); i++) { + for (int i = size - sizeof(T) + 1; i <= (int)(size + 2 * sizeof(T)); i++) { const char *str = "is located.*%d byte.*to the right"; int off = i >= size ? (i - size) : 0; @@ -304,8 +301,20 @@ TEST(AddressSanitizer, OOBRightTest) { } } +#if ASAN_ALLOCATOR_VERSION == 2 // Broken with the asan_allocator1 +TEST(AddressSanitizer, LargeOOBRightTest) { + size_t large_power_of_two = 1 << 19; + for (size_t i = 16; i <= 256; i *= 2) { + size_t size = large_power_of_two - i; + char *p = Ident(new char[size]); + EXPECT_DEATH(p[size] = 0, "is located 0 bytes to the right"); + delete [] p; + } +} +#endif // ASAN_ALLOCATOR_VERSION == 2 + TEST(AddressSanitizer, UAF_char) { - const char *uaf_string = "AddressSanitizer.*heap-use-after-free"; + const char *uaf_string = "AddressSanitizer:.*heap-use-after-free"; EXPECT_DEATH(uaf_test<U1>(1, 0), uaf_string); EXPECT_DEATH(uaf_test<U1>(10, 0), uaf_string); EXPECT_DEATH(uaf_test<U1>(10, 10), uaf_string); @@ -335,7 +344,7 @@ TEST(AddressSanitizer, BitFieldPositiveTest) { EXPECT_DEATH(x->bf2 = 0, "use-after-free"); EXPECT_DEATH(x->bf3 = 0, "use-after-free"); EXPECT_DEATH(x->bf4 = 0, "use-after-free"); -}; +} struct StructWithBitFields_8_24 { int a:8; @@ -350,7 +359,7 @@ TEST(AddressSanitizer, BitFieldNegativeTest) { } TEST(AddressSanitizer, OutOfMemoryTest) { - size_t size = __WORDSIZE == 64 ? (size_t)(1ULL << 48) : (0xf0000000); + size_t size = SANITIZER_WORDSIZE == 64 ? (size_t)(1ULL << 48) : (0xf0000000); EXPECT_EQ(0, realloc(0, size)); EXPECT_EQ(0, realloc(0, ~Ident(0))); EXPECT_EQ(0, malloc(size)); @@ -360,28 +369,61 @@ TEST(AddressSanitizer, OutOfMemoryTest) { } #if ASAN_NEEDS_SEGV +namespace { + +const char kUnknownCrash[] = "AddressSanitizer: SEGV on unknown address"; +const char kOverriddenHandler[] = "ASan signal handler has been overridden\n"; + TEST(AddressSanitizer, WildAddressTest) { char *c = (char*)0x123; - EXPECT_DEATH(*c = 0, "AddressSanitizer crashed on unknown address"); + EXPECT_DEATH(*c = 0, kUnknownCrash); +} + +void my_sigaction_sighandler(int, siginfo_t*, void*) { + fprintf(stderr, kOverriddenHandler); + exit(1); } + +void my_signal_sighandler(int signum) { + fprintf(stderr, kOverriddenHandler); + exit(1); +} + +TEST(AddressSanitizer, SignalTest) { + struct sigaction sigact; + memset(&sigact, 0, sizeof(sigact)); + sigact.sa_sigaction = my_sigaction_sighandler; + sigact.sa_flags = SA_SIGINFO; + // ASan should silently ignore sigaction()... + EXPECT_EQ(0, sigaction(SIGSEGV, &sigact, 0)); +#ifdef __APPLE__ + EXPECT_EQ(0, sigaction(SIGBUS, &sigact, 0)); +#endif + char *c = (char*)0x123; + EXPECT_DEATH(*c = 0, kUnknownCrash); + // ... and signal(). + EXPECT_EQ(0, signal(SIGSEGV, my_signal_sighandler)); + EXPECT_DEATH(*c = 0, kUnknownCrash); +} +} // namespace #endif static void MallocStress(size_t n) { - uint32_t seed = my_rand(&global_seed); + uint32_t seed = my_rand(); for (size_t iter = 0; iter < 10; iter++) { vector<void *> vec; for (size_t i = 0; i < n; i++) { if ((i % 3) == 0) { if (vec.empty()) continue; - size_t idx = my_rand(&seed) % vec.size(); + size_t idx = my_rand_r(&seed) % vec.size(); void *ptr = vec[idx]; vec[idx] = vec.back(); vec.pop_back(); free_aaa(ptr); } else { - size_t size = my_rand(&seed) % 1000 + 1; + size_t size = my_rand_r(&seed) % 1000 + 1; #ifndef __APPLE__ - size_t alignment = 1 << (my_rand(&seed) % 7 + 3); + size_t alignment = 1 << (my_rand_r(&seed) % 7 + 3); char *ptr = (char*)memalign_aaa(alignment, size); #else char *ptr = (char*) malloc_aaa(size); @@ -421,24 +463,42 @@ TEST(AddressSanitizer, HugeMallocTest) { // 32-bit Mac 10.7 gives even less (< 1G). // (the libSystem malloc() allows allocating up to 2300 megabytes without // ASan). - size_t n_megs = __WORDSIZE == 32 ? 500 : 4100; + size_t n_megs = SANITIZER_WORDSIZE == 32 ? 500 : 4100; #else - size_t n_megs = __WORDSIZE == 32 ? 2600 : 4100; + size_t n_megs = SANITIZER_WORDSIZE == 32 ? 2600 : 4100; #endif TestLargeMalloc(n_megs << 20); } #endif +#ifndef __APPLE__ +void MemalignRun(size_t align, size_t size, int idx) { + char *p = (char *)memalign(align, size); + Ident(p)[idx] = 0; + free(p); +} + +TEST(AddressSanitizer, memalign) { + for (int align = 16; align <= (1 << 23); align *= 2) { + size_t size = align * 5; + EXPECT_DEATH(MemalignRun(align, size, -1), + "is located 1 bytes to the left"); + EXPECT_DEATH(MemalignRun(align, size, size + 1), + "is located 1 bytes to the right"); + } +} +#endif + TEST(AddressSanitizer, ThreadedMallocStressTest) { const int kNumThreads = 4; const int kNumIterations = (ASAN_LOW_MEMORY) ? 10000 : 100000; pthread_t t[kNumThreads]; for (int i = 0; i < kNumThreads; i++) { - pthread_create(&t[i], 0, (void* (*)(void *x))MallocStress, + PTHREAD_CREATE(&t[i], 0, (void* (*)(void *x))MallocStress, (void*)kNumIterations); } for (int i = 0; i < kNumThreads; i++) { - pthread_join(t[i], 0); + PTHREAD_JOIN(t[i], 0); } } @@ -452,13 +512,14 @@ void *ManyThreadsWorker(void *a) { } TEST(AddressSanitizer, ManyThreadsTest) { - const size_t kNumThreads = __WORDSIZE == 32 ? 30 : 1000; + const size_t kNumThreads = + (SANITIZER_WORDSIZE == 32 || ASAN_AVOID_EXPENSIVE_TESTS) ? 30 : 1000; pthread_t t[kNumThreads]; for (size_t i = 0; i < kNumThreads; i++) { - pthread_create(&t[i], 0, (void* (*)(void *x))ManyThreadsWorker, (void*)i); + PTHREAD_CREATE(&t[i], 0, ManyThreadsWorker, (void*)i); } for (size_t i = 0; i < kNumThreads; i++) { - pthread_join(t[i], 0); + PTHREAD_JOIN(t[i], 0); } } @@ -468,20 +529,20 @@ TEST(AddressSanitizer, ReallocTest) { ptr[3] = 3; for (int i = 0; i < 10000; i++) { ptr = (int*)realloc(ptr, - (my_rand(&global_seed) % 1000 + kMinElem) * sizeof(int)); + (my_rand() % 1000 + kMinElem) * sizeof(int)); EXPECT_EQ(3, ptr[3]); } } #ifndef __APPLE__ static const char *kMallocUsableSizeErrorMsg = - "AddressSanitizer attempting to call malloc_usable_size()"; + "AddressSanitizer: attempting to call malloc_usable_size()"; TEST(AddressSanitizer, MallocUsableSizeTest) { const size_t kArraySize = 100; char *array = Ident((char*)malloc(kArraySize)); int *int_ptr = Ident(new int); - EXPECT_EQ(0, malloc_usable_size(NULL)); + EXPECT_EQ(0U, malloc_usable_size(NULL)); EXPECT_EQ(kArraySize, malloc_usable_size(array)); EXPECT_EQ(sizeof(int), malloc_usable_size(int_ptr)); EXPECT_DEATH(malloc_usable_size((void*)0x123), kMallocUsableSizeErrorMsg); @@ -501,7 +562,7 @@ void WrongFree() { TEST(AddressSanitizer, WrongFreeTest) { EXPECT_DEATH(WrongFree(), - "ERROR: AddressSanitizer attempting free.*not malloc"); + "ERROR: AddressSanitizer: attempting free.*not malloc"); } void DoubleFree() { @@ -515,7 +576,7 @@ void DoubleFree() { TEST(AddressSanitizer, DoubleFreeTest) { EXPECT_DEATH(DoubleFree(), ASAN_PCRE_DOTALL - "ERROR: AddressSanitizer attempting double-free" + "ERROR: AddressSanitizer: attempting double-free" ".*is located 0 bytes inside of 400-byte region" ".*freed by thread T0 here" ".*previously allocated by thread T0 here"); @@ -610,6 +671,17 @@ NOINLINE void LongJmpFunc1(jmp_buf buf) { longjmp(buf, 1); } +NOINLINE void BuiltinLongJmpFunc1(jmp_buf buf) { + // create three red zones for these two stack objects. + int a; + int b; + + int *A = Ident(&a); + int *B = Ident(&b); + *A = *B; + __builtin_longjmp((void**)buf, 1); +} + NOINLINE void UnderscopeLongJmpFunc1(jmp_buf buf) { // create three red zones for these two stack objects. int a; @@ -650,6 +722,17 @@ TEST(AddressSanitizer, LongJmpTest) { } } +#if not defined(__ANDROID__) +TEST(AddressSanitizer, BuiltinLongJmpTest) { + static jmp_buf buf; + if (!__builtin_setjmp((void**)buf)) { + BuiltinLongJmpFunc1(buf); + } else { + TouchStackFunc(); + } +} +#endif // not defined(__ANDROID__) + TEST(AddressSanitizer, UnderscopeLongJmpTest) { static jmp_buf buf; if (!_setjmp(buf)) { @@ -683,7 +766,7 @@ NOINLINE void ThrowFunc() { TEST(AddressSanitizer, CxxExceptionTest) { if (ASAN_UAR) return; // TODO(kcc): this test crashes on 32-bit for some reason... - if (__WORDSIZE == 32) return; + if (SANITIZER_WORDSIZE == 32) return; try { ThrowFunc(); } catch(...) {} @@ -710,10 +793,10 @@ void *ThreadStackReuseFunc2(void *unused) { TEST(AddressSanitizer, ThreadStackReuseTest) { pthread_t t; - pthread_create(&t, 0, ThreadStackReuseFunc1, 0); - pthread_join(t, 0); - pthread_create(&t, 0, ThreadStackReuseFunc2, 0); - pthread_join(t, 0); + PTHREAD_CREATE(&t, 0, ThreadStackReuseFunc1, 0); + PTHREAD_JOIN(t, 0); + PTHREAD_CREATE(&t, 0, ThreadStackReuseFunc2, 0); + PTHREAD_JOIN(t, 0); } #if defined(__i386__) || defined(__x86_64__) @@ -725,7 +808,7 @@ TEST(AddressSanitizer, Store128Test) { assert(((uintptr_t)p % 16) == 0); __m128i value_wide = _mm_set1_epi16(0x1234); EXPECT_DEATH(_mm_store_si128((__m128i*)p, value_wide), - "AddressSanitizer heap-buffer-overflow"); + "AddressSanitizer: heap-buffer-overflow"); EXPECT_DEATH(_mm_store_si128((__m128i*)p, value_wide), "WRITE of size 16"); EXPECT_DEATH(_mm_store_si128((__m128i*)p, value_wide), @@ -734,14 +817,39 @@ TEST(AddressSanitizer, Store128Test) { } #endif -static string RightOOBErrorMessage(int oob_distance) { +static string RightOOBErrorMessage(int oob_distance, bool is_write) { assert(oob_distance >= 0); char expected_str[100]; - sprintf(expected_str, "located %d bytes to the right", oob_distance); + sprintf(expected_str, ASAN_PCRE_DOTALL "%s.*located %d bytes to the right", + is_write ? "WRITE" : "READ", oob_distance); return string(expected_str); } -static string LeftOOBErrorMessage(int oob_distance) { +static string RightOOBWriteMessage(int oob_distance) { + return RightOOBErrorMessage(oob_distance, /*is_write*/true); +} + +static string RightOOBReadMessage(int oob_distance) { + return RightOOBErrorMessage(oob_distance, /*is_write*/false); +} + +static string LeftOOBErrorMessage(int oob_distance, bool is_write) { + assert(oob_distance > 0); + char expected_str[100]; + sprintf(expected_str, ASAN_PCRE_DOTALL "%s.*located %d bytes to the left", + is_write ? "WRITE" : "READ", oob_distance); + return string(expected_str); +} + +static string LeftOOBWriteMessage(int oob_distance) { + return LeftOOBErrorMessage(oob_distance, /*is_write*/true); +} + +static string LeftOOBReadMessage(int oob_distance) { + return LeftOOBErrorMessage(oob_distance, /*is_write*/false); +} + +static string LeftOOBAccessMessage(int oob_distance) { assert(oob_distance > 0); char expected_str[100]; sprintf(expected_str, "located %d bytes to the left", oob_distance); @@ -755,44 +863,48 @@ void MemSetOOBTestTemplate(size_t length) { T *array = Ident((T*)malloc(size)); int element = Ident(42); int zero = Ident(0); + void *(*MEMSET)(void *s, int c, size_t n) = Ident(memset); // memset interval inside array - memset(array, element, size); - memset(array, element, size - 1); - memset(array + length - 1, element, sizeof(T)); - memset(array, element, 1); + MEMSET(array, element, size); + MEMSET(array, element, size - 1); + MEMSET(array + length - 1, element, sizeof(T)); + MEMSET(array, element, 1); // memset 0 bytes - memset(array - 10, element, zero); - memset(array - 1, element, zero); - memset(array, element, zero); - memset(array + length, 0, zero); - memset(array + length + 1, 0, zero); + MEMSET(array - 10, element, zero); + MEMSET(array - 1, element, zero); + MEMSET(array, element, zero); + MEMSET(array + length, 0, zero); + MEMSET(array + length + 1, 0, zero); // try to memset bytes to the right of array - EXPECT_DEATH(memset(array, 0, size + 1), - RightOOBErrorMessage(0)); - EXPECT_DEATH(memset((char*)(array + length) - 1, element, 6), - RightOOBErrorMessage(4)); - EXPECT_DEATH(memset(array + 1, element, size + sizeof(T)), - RightOOBErrorMessage(2 * sizeof(T) - 1)); + EXPECT_DEATH(MEMSET(array, 0, size + 1), + RightOOBWriteMessage(0)); + EXPECT_DEATH(MEMSET((char*)(array + length) - 1, element, 6), + RightOOBWriteMessage(0)); + EXPECT_DEATH(MEMSET(array + 1, element, size + sizeof(T)), + RightOOBWriteMessage(0)); // whole interval is to the right - EXPECT_DEATH(memset(array + length + 1, 0, 10), - RightOOBErrorMessage(sizeof(T))); + EXPECT_DEATH(MEMSET(array + length + 1, 0, 10), + RightOOBWriteMessage(sizeof(T))); // try to memset bytes to the left of array - EXPECT_DEATH(memset((char*)array - 1, element, size), - LeftOOBErrorMessage(1)); - EXPECT_DEATH(memset((char*)array - 5, 0, 6), - LeftOOBErrorMessage(5)); - EXPECT_DEATH(memset(array - 5, element, size + 5 * sizeof(T)), - LeftOOBErrorMessage(5 * sizeof(T))); + EXPECT_DEATH(MEMSET((char*)array - 1, element, size), + LeftOOBWriteMessage(1)); + EXPECT_DEATH(MEMSET((char*)array - 5, 0, 6), + LeftOOBWriteMessage(5)); + if (length >= 100) { + // Large OOB, we find it only if the redzone is large enough. + EXPECT_DEATH(memset(array - 5, element, size + 5 * sizeof(T)), + LeftOOBWriteMessage(5 * sizeof(T))); + } // whole interval is to the left - EXPECT_DEATH(memset(array - 2, 0, sizeof(T)), - LeftOOBErrorMessage(2 * sizeof(T))); + EXPECT_DEATH(MEMSET(array - 2, 0, sizeof(T)), + LeftOOBWriteMessage(2 * sizeof(T))); // try to memset bytes both to the left & to the right - EXPECT_DEATH(memset((char*)array - 2, element, size + 4), - LeftOOBErrorMessage(2)); + EXPECT_DEATH(MEMSET((char*)array - 2, element, size + 4), + LeftOOBWriteMessage(2)); free(array); } @@ -804,6 +916,51 @@ TEST(AddressSanitizer, MemSetOOBTest) { // We can test arrays of structres/classes here, but what for? } +// Try to allocate two arrays of 'size' bytes that are near each other. +// Strictly speaking we are not guaranteed to find such two pointers, +// but given the structure of asan's allocator we will. +static bool AllocateTwoAdjacentArrays(char **x1, char **x2, size_t size) { + vector<char *> v; + bool res = false; + for (size_t i = 0; i < 1000U && !res; i++) { + v.push_back(new char[size]); + if (i == 0) continue; + sort(v.begin(), v.end()); + for (size_t j = 1; j < v.size(); j++) { + assert(v[j] > v[j-1]); + if ((size_t)(v[j] - v[j-1]) < size * 2) { + *x2 = v[j]; + *x1 = v[j-1]; + res = true; + break; + } + } + } + + for (size_t i = 0; i < v.size(); i++) { + if (res && v[i] == *x1) continue; + if (res && v[i] == *x2) continue; + delete [] v[i]; + } + return res; +} + +TEST(AddressSanitizer, LargeOOBInMemset) { + for (size_t size = 200; size < 100000; size += size / 2) { + char *x1, *x2; + if (!Ident(AllocateTwoAdjacentArrays)(&x1, &x2, size)) + continue; + // fprintf(stderr, " large oob memset: %p %p %zd\n", x1, x2, size); + // Do a memset on x1 with huge out-of-bound access that will end up in x2. + EXPECT_DEATH(Ident(memset)(x1, 0, size * 2), + "is located 0 bytes to the right"); + delete [] x1; + delete [] x2; + return; + } + assert(0 && "Did not find two adjacent malloc-ed pointers"); +} + // Same test for memcpy and memmove functions template <typename T, class M> void MemTransferOOBTestTemplate(size_t length) { @@ -827,27 +984,27 @@ void MemTransferOOBTestTemplate(size_t length) { // try to change mem to the right of dest EXPECT_DEATH(M::transfer(dest + 1, src, size), - RightOOBErrorMessage(sizeof(T) - 1)); + RightOOBWriteMessage(0)); EXPECT_DEATH(M::transfer((char*)(dest + length) - 1, src, 5), - RightOOBErrorMessage(3)); + RightOOBWriteMessage(0)); // try to change mem to the left of dest EXPECT_DEATH(M::transfer(dest - 2, src, size), - LeftOOBErrorMessage(2 * sizeof(T))); + LeftOOBWriteMessage(2 * sizeof(T))); EXPECT_DEATH(M::transfer((char*)dest - 3, src, 4), - LeftOOBErrorMessage(3)); + LeftOOBWriteMessage(3)); // try to access mem to the right of src EXPECT_DEATH(M::transfer(dest, src + 2, size), - RightOOBErrorMessage(2 * sizeof(T) - 1)); + RightOOBReadMessage(0)); EXPECT_DEATH(M::transfer(dest, (char*)(src + length) - 3, 6), - RightOOBErrorMessage(2)); + RightOOBReadMessage(0)); // try to access mem to the left of src EXPECT_DEATH(M::transfer(dest, src - 1, size), - LeftOOBErrorMessage(sizeof(T))); + LeftOOBReadMessage(sizeof(T))); EXPECT_DEATH(M::transfer(dest, (char*)src - 6, 7), - LeftOOBErrorMessage(6)); + LeftOOBReadMessage(6)); // Generally we don't need to test cases where both accessing src and writing // to dest address to poisoned memory. @@ -856,10 +1013,10 @@ void MemTransferOOBTestTemplate(size_t length) { T *big_dest = Ident((T*)malloc(size * 2)); // try to change mem to both sides of dest EXPECT_DEATH(M::transfer(dest - 1, big_src, size * 2), - LeftOOBErrorMessage(sizeof(T))); + LeftOOBWriteMessage(sizeof(T))); // try to access mem to both sides of src EXPECT_DEATH(M::transfer(big_dest, src - 2, size * 2), - LeftOOBErrorMessage(2 * sizeof(T))); + LeftOOBReadMessage(2 * sizeof(T))); free(src); free(dest); @@ -870,7 +1027,7 @@ void MemTransferOOBTestTemplate(size_t length) { class MemCpyWrapper { public: static void* transfer(void *to, const void *from, size_t size) { - return memcpy(to, from, size); + return Ident(memcpy)(to, from, size); } }; TEST(AddressSanitizer, MemCpyOOBTest) { @@ -881,7 +1038,7 @@ TEST(AddressSanitizer, MemCpyOOBTest) { class MemMoveWrapper { public: static void* transfer(void *to, const void *from, size_t size) { - return memmove(to, from, size); + return Ident(memmove)(to, from, size); } }; TEST(AddressSanitizer, MemMoveOOBTest) { @@ -902,21 +1059,21 @@ void StrLenOOBTestTemplate(char *str, size_t length, bool is_global) { // Normal strlen calls EXPECT_EQ(strlen(str), length); if (length > 0) { - EXPECT_EQ(strlen(str + 1), length - 1); - EXPECT_EQ(strlen(str + length), 0); + EXPECT_EQ(length - 1, strlen(str + 1)); + EXPECT_EQ(0U, strlen(str + length)); } // Arg of strlen is not malloced, OOB access if (!is_global) { // We don't insert RedZones to the left of global variables - EXPECT_DEATH(Ident(strlen(str - 1)), LeftOOBErrorMessage(1)); - EXPECT_DEATH(Ident(strlen(str - 5)), LeftOOBErrorMessage(5)); + EXPECT_DEATH(Ident(strlen(str - 1)), LeftOOBReadMessage(1)); + EXPECT_DEATH(Ident(strlen(str - 5)), LeftOOBReadMessage(5)); } - EXPECT_DEATH(Ident(strlen(str + length + 1)), RightOOBErrorMessage(0)); + EXPECT_DEATH(Ident(strlen(str + length + 1)), RightOOBReadMessage(0)); // Overwrite terminator str[length] = 'a'; // String is not zero-terminated, strlen will lead to OOB access - EXPECT_DEATH(Ident(strlen(str)), RightOOBErrorMessage(0)); - EXPECT_DEATH(Ident(strlen(str + length)), RightOOBErrorMessage(0)); + EXPECT_DEATH(Ident(strlen(str)), RightOOBReadMessage(0)); + EXPECT_DEATH(Ident(strlen(str + length)), RightOOBReadMessage(0)); // Restore terminator str[length] = 0; } @@ -925,7 +1082,8 @@ TEST(AddressSanitizer, StrLenOOBTest) { size_t length = Ident(10); char *heap_string = Ident((char*)malloc(length + 1)); char stack_string[10 + 1]; - for (int i = 0; i < length; i++) { + break_optimization(&stack_string); + for (size_t i = 0; i < length; i++) { heap_string[i] = 'a'; stack_string[i] = 'b'; } @@ -959,11 +1117,11 @@ TEST(AddressSanitizer, StrNLenOOBTest) { str[size - 1] = '\0'; Ident(strnlen(str, 2 * size)); // Argument points to not allocated memory. - EXPECT_DEATH(Ident(strnlen(str - 1, 1)), LeftOOBErrorMessage(1)); - EXPECT_DEATH(Ident(strnlen(str + size, 1)), RightOOBErrorMessage(0)); + EXPECT_DEATH(Ident(strnlen(str - 1, 1)), LeftOOBReadMessage(1)); + EXPECT_DEATH(Ident(strnlen(str + size, 1)), RightOOBReadMessage(0)); // Overwrite the terminating '\0' and hit unallocated memory. str[size - 1] = 'z'; - EXPECT_DEATH(Ident(strnlen(str, size + 1)), RightOOBErrorMessage(0)); + EXPECT_DEATH(Ident(strnlen(str, size + 1)), RightOOBReadMessage(0)); free(str); } #endif @@ -979,11 +1137,11 @@ TEST(AddressSanitizer, StrDupOOBTest) { new_str = strdup(str + size - 1); free(new_str); // Argument points to not allocated memory. - EXPECT_DEATH(Ident(strdup(str - 1)), LeftOOBErrorMessage(1)); - EXPECT_DEATH(Ident(strdup(str + size)), RightOOBErrorMessage(0)); + EXPECT_DEATH(Ident(strdup(str - 1)), LeftOOBReadMessage(1)); + EXPECT_DEATH(Ident(strdup(str + size)), RightOOBReadMessage(0)); // Overwrite the terminating '\0' and hit unallocated memory. str[size - 1] = 'z'; - EXPECT_DEATH(Ident(strdup(str)), RightOOBErrorMessage(0)); + EXPECT_DEATH(Ident(strdup(str)), RightOOBReadMessage(0)); free(str); } @@ -997,15 +1155,15 @@ TEST(AddressSanitizer, StrCpyOOBTest) { strcpy(to, from); strcpy(to + to_size - from_size, from); // Length of "from" is too small. - EXPECT_DEATH(Ident(strcpy(from, "hello2")), RightOOBErrorMessage(0)); + EXPECT_DEATH(Ident(strcpy(from, "hello2")), RightOOBWriteMessage(0)); // "to" or "from" points to not allocated memory. - EXPECT_DEATH(Ident(strcpy(to - 1, from)), LeftOOBErrorMessage(1)); - EXPECT_DEATH(Ident(strcpy(to, from - 1)), LeftOOBErrorMessage(1)); - EXPECT_DEATH(Ident(strcpy(to, from + from_size)), RightOOBErrorMessage(0)); - EXPECT_DEATH(Ident(strcpy(to + to_size, from)), RightOOBErrorMessage(0)); + EXPECT_DEATH(Ident(strcpy(to - 1, from)), LeftOOBWriteMessage(1)); + EXPECT_DEATH(Ident(strcpy(to, from - 1)), LeftOOBReadMessage(1)); + EXPECT_DEATH(Ident(strcpy(to, from + from_size)), RightOOBReadMessage(0)); + EXPECT_DEATH(Ident(strcpy(to + to_size, from)), RightOOBWriteMessage(0)); // Overwrite the terminating '\0' character and hit unallocated memory. from[from_size - 1] = '!'; - EXPECT_DEATH(Ident(strcpy(to, from)), RightOOBErrorMessage(0)); + EXPECT_DEATH(Ident(strcpy(to, from)), RightOOBReadMessage(0)); free(to); free(from); } @@ -1027,31 +1185,37 @@ TEST(AddressSanitizer, StrNCpyOOBTest) { strncpy(to + to_size - 1, from, 1); // One of {to, from} points to not allocated memory EXPECT_DEATH(Ident(strncpy(to, from - 1, from_size)), - LeftOOBErrorMessage(1)); + LeftOOBReadMessage(1)); EXPECT_DEATH(Ident(strncpy(to - 1, from, from_size)), - LeftOOBErrorMessage(1)); + LeftOOBWriteMessage(1)); EXPECT_DEATH(Ident(strncpy(to, from + from_size, 1)), - RightOOBErrorMessage(0)); + RightOOBReadMessage(0)); EXPECT_DEATH(Ident(strncpy(to + to_size, from, 1)), - RightOOBErrorMessage(0)); + RightOOBWriteMessage(0)); // Length of "to" is too small EXPECT_DEATH(Ident(strncpy(to + to_size - from_size + 1, from, from_size)), - RightOOBErrorMessage(0)); + RightOOBWriteMessage(0)); EXPECT_DEATH(Ident(strncpy(to + 1, from, to_size)), - RightOOBErrorMessage(0)); + RightOOBWriteMessage(0)); // Overwrite terminator in from from[from_size - 1] = '!'; // normal strncpy call strncpy(to, from, from_size); // Length of "from" is too small EXPECT_DEATH(Ident(strncpy(to, from, to_size)), - RightOOBErrorMessage(0)); + RightOOBReadMessage(0)); free(to); free(from); } -typedef char*(*PointerToStrChr)(const char*, int); -void RunStrChrTest(PointerToStrChr StrChr) { +// Users may have different definitions of "strchr" and "index", so provide +// function pointer typedefs and overload RunStrChrTest implementation. +// We can't use macro for RunStrChrTest body here, as this macro would +// confuse EXPECT_DEATH gtest macro. +typedef char*(*PointerToStrChr1)(const char*, int); +typedef char*(*PointerToStrChr2)(char*, int); + +USED static void RunStrChrTest(PointerToStrChr1 StrChr) { size_t size = Ident(100); char *str = MallocAndMemsetString(size); str[10] = 'q'; @@ -1060,13 +1224,30 @@ void RunStrChrTest(PointerToStrChr StrChr) { EXPECT_EQ(str + 10, StrChr(str, 'q')); EXPECT_EQ(NULL, StrChr(str, 'a')); // StrChr argument points to not allocated memory. - EXPECT_DEATH(Ident(StrChr(str - 1, 'z')), LeftOOBErrorMessage(1)); - EXPECT_DEATH(Ident(StrChr(str + size, 'z')), RightOOBErrorMessage(0)); + EXPECT_DEATH(Ident(StrChr(str - 1, 'z')), LeftOOBReadMessage(1)); + EXPECT_DEATH(Ident(StrChr(str + size, 'z')), RightOOBReadMessage(0)); // Overwrite the terminator and hit not allocated memory. str[11] = 'z'; - EXPECT_DEATH(Ident(StrChr(str, 'a')), RightOOBErrorMessage(0)); + EXPECT_DEATH(Ident(StrChr(str, 'a')), RightOOBReadMessage(0)); free(str); } +USED static void RunStrChrTest(PointerToStrChr2 StrChr) { + size_t size = Ident(100); + char *str = MallocAndMemsetString(size); + str[10] = 'q'; + str[11] = '\0'; + EXPECT_EQ(str, StrChr(str, 'z')); + EXPECT_EQ(str + 10, StrChr(str, 'q')); + EXPECT_EQ(NULL, StrChr(str, 'a')); + // StrChr argument points to not allocated memory. + EXPECT_DEATH(Ident(StrChr(str - 1, 'z')), LeftOOBReadMessage(1)); + EXPECT_DEATH(Ident(StrChr(str + size, 'z')), RightOOBReadMessage(0)); + // Overwrite the terminator and hit not allocated memory. + str[11] = 'z'; + EXPECT_DEATH(Ident(StrChr(str, 'a')), RightOOBReadMessage(0)); + free(str); +} + TEST(AddressSanitizer, StrChrAndIndexOOBTest) { RunStrChrTest(&strchr); RunStrChrTest(&index); @@ -1124,8 +1305,9 @@ TEST(AddressSanitizer, StrCmpAndFriendsLogicTest) { typedef int(*PointerToStrCmp)(const char*, const char*); void RunStrCmpTest(PointerToStrCmp StrCmp) { size_t size = Ident(100); - char *s1 = MallocAndMemsetString(size); - char *s2 = MallocAndMemsetString(size); + int fill = 'o'; + char *s1 = MallocAndMemsetString(size, fill); + char *s2 = MallocAndMemsetString(size, fill); s1[size - 1] = '\0'; s2[size - 1] = '\0'; // Normal StrCmp calls @@ -1136,14 +1318,14 @@ void RunStrCmpTest(PointerToStrCmp StrCmp) { s2[size - 1] = 'x'; Ident(StrCmp(s1, s2)); // One of arguments points to not allocated memory. - EXPECT_DEATH(Ident(StrCmp)(s1 - 1, s2), LeftOOBErrorMessage(1)); - EXPECT_DEATH(Ident(StrCmp)(s1, s2 - 1), LeftOOBErrorMessage(1)); - EXPECT_DEATH(Ident(StrCmp)(s1 + size, s2), RightOOBErrorMessage(0)); - EXPECT_DEATH(Ident(StrCmp)(s1, s2 + size), RightOOBErrorMessage(0)); + EXPECT_DEATH(Ident(StrCmp)(s1 - 1, s2), LeftOOBReadMessage(1)); + EXPECT_DEATH(Ident(StrCmp)(s1, s2 - 1), LeftOOBReadMessage(1)); + EXPECT_DEATH(Ident(StrCmp)(s1 + size, s2), RightOOBReadMessage(0)); + EXPECT_DEATH(Ident(StrCmp)(s1, s2 + size), RightOOBReadMessage(0)); // Hit unallocated memory and die. - s2[size - 1] = 'z'; - EXPECT_DEATH(Ident(StrCmp)(s1, s1), RightOOBErrorMessage(0)); - EXPECT_DEATH(Ident(StrCmp)(s1 + size - 1, s2), RightOOBErrorMessage(0)); + s1[size - 1] = fill; + EXPECT_DEATH(Ident(StrCmp)(s1, s1), RightOOBReadMessage(0)); + EXPECT_DEATH(Ident(StrCmp)(s1 + size - 1, s2), RightOOBReadMessage(0)); free(s1); free(s2); } @@ -1172,13 +1354,13 @@ void RunStrNCmpTest(PointerToStrNCmp StrNCmp) { Ident(StrNCmp(s1 - 1, s2 - 1, 0)); Ident(StrNCmp(s1 + size - 1, s2 + size - 1, 1)); // One of arguments points to not allocated memory. - EXPECT_DEATH(Ident(StrNCmp)(s1 - 1, s2, 1), LeftOOBErrorMessage(1)); - EXPECT_DEATH(Ident(StrNCmp)(s1, s2 - 1, 1), LeftOOBErrorMessage(1)); - EXPECT_DEATH(Ident(StrNCmp)(s1 + size, s2, 1), RightOOBErrorMessage(0)); - EXPECT_DEATH(Ident(StrNCmp)(s1, s2 + size, 1), RightOOBErrorMessage(0)); + EXPECT_DEATH(Ident(StrNCmp)(s1 - 1, s2, 1), LeftOOBReadMessage(1)); + EXPECT_DEATH(Ident(StrNCmp)(s1, s2 - 1, 1), LeftOOBReadMessage(1)); + EXPECT_DEATH(Ident(StrNCmp)(s1 + size, s2, 1), RightOOBReadMessage(0)); + EXPECT_DEATH(Ident(StrNCmp)(s1, s2 + size, 1), RightOOBReadMessage(0)); // Hit unallocated memory and die. - EXPECT_DEATH(Ident(StrNCmp)(s1 + 1, s2 + 1, size), RightOOBErrorMessage(0)); - EXPECT_DEATH(Ident(StrNCmp)(s1 + size - 1, s2, 2), RightOOBErrorMessage(0)); + EXPECT_DEATH(Ident(StrNCmp)(s1 + 1, s2 + 1, size), RightOOBReadMessage(0)); + EXPECT_DEATH(Ident(StrNCmp)(s1 + size - 1, s2, 2), RightOOBReadMessage(0)); free(s1); free(s2); } @@ -1200,22 +1382,23 @@ TEST(AddressSanitizer, MemCmpOOBTest) { Ident(memcmp(s1 + size - 1, s2 + size - 1, 1)); Ident(memcmp(s1 - 1, s2 - 1, 0)); // One of arguments points to not allocated memory. - EXPECT_DEATH(Ident(memcmp)(s1 - 1, s2, 1), LeftOOBErrorMessage(1)); - EXPECT_DEATH(Ident(memcmp)(s1, s2 - 1, 1), LeftOOBErrorMessage(1)); - EXPECT_DEATH(Ident(memcmp)(s1 + size, s2, 1), RightOOBErrorMessage(0)); - EXPECT_DEATH(Ident(memcmp)(s1, s2 + size, 1), RightOOBErrorMessage(0)); + EXPECT_DEATH(Ident(memcmp)(s1 - 1, s2, 1), LeftOOBReadMessage(1)); + EXPECT_DEATH(Ident(memcmp)(s1, s2 - 1, 1), LeftOOBReadMessage(1)); + EXPECT_DEATH(Ident(memcmp)(s1 + size, s2, 1), RightOOBReadMessage(0)); + EXPECT_DEATH(Ident(memcmp)(s1, s2 + size, 1), RightOOBReadMessage(0)); // Hit unallocated memory and die. - EXPECT_DEATH(Ident(memcmp)(s1 + 1, s2 + 1, size), RightOOBErrorMessage(0)); - EXPECT_DEATH(Ident(memcmp)(s1 + size - 1, s2, 2), RightOOBErrorMessage(0)); + EXPECT_DEATH(Ident(memcmp)(s1 + 1, s2 + 1, size), RightOOBReadMessage(0)); + EXPECT_DEATH(Ident(memcmp)(s1 + size - 1, s2, 2), RightOOBReadMessage(0)); // Zero bytes are not terminators and don't prevent from OOB. s1[size - 1] = '\0'; s2[size - 1] = '\0'; - EXPECT_DEATH(Ident(memcmp)(s1, s2, size + 1), RightOOBErrorMessage(0)); + EXPECT_DEATH(Ident(memcmp)(s1, s2, size + 1), RightOOBReadMessage(0)); free(s1); free(s2); } TEST(AddressSanitizer, StrCatOOBTest) { + // strcat() reads strlen(to) bytes from |to| before concatenating. size_t to_size = Ident(100); char *to = MallocAndMemsetString(to_size); to[0] = '\0'; @@ -1226,24 +1409,25 @@ TEST(AddressSanitizer, StrCatOOBTest) { strcat(to, from); strcat(to, from); strcat(to + from_size, from + from_size - 2); - // Catenate empty string is not always an error. - strcat(to - 1, from + from_size - 1); + // Passing an invalid pointer is an error even when concatenating an empty + // string. + EXPECT_DEATH(strcat(to - 1, from + from_size - 1), LeftOOBAccessMessage(1)); // One of arguments points to not allocated memory. - EXPECT_DEATH(strcat(to - 1, from), LeftOOBErrorMessage(1)); - EXPECT_DEATH(strcat(to, from - 1), LeftOOBErrorMessage(1)); - EXPECT_DEATH(strcat(to + to_size, from), RightOOBErrorMessage(0)); - EXPECT_DEATH(strcat(to, from + from_size), RightOOBErrorMessage(0)); + EXPECT_DEATH(strcat(to - 1, from), LeftOOBAccessMessage(1)); + EXPECT_DEATH(strcat(to, from - 1), LeftOOBReadMessage(1)); + EXPECT_DEATH(strcat(to + to_size, from), RightOOBWriteMessage(0)); + EXPECT_DEATH(strcat(to, from + from_size), RightOOBReadMessage(0)); // "from" is not zero-terminated. from[from_size - 1] = 'z'; - EXPECT_DEATH(strcat(to, from), RightOOBErrorMessage(0)); + EXPECT_DEATH(strcat(to, from), RightOOBReadMessage(0)); from[from_size - 1] = '\0'; // "to" is not zero-terminated. memset(to, 'z', to_size); - EXPECT_DEATH(strcat(to, from), RightOOBErrorMessage(0)); + EXPECT_DEATH(strcat(to, from), RightOOBWriteMessage(0)); // "to" is too short to fit "from". to[to_size - from_size + 1] = '\0'; - EXPECT_DEATH(strcat(to, from), RightOOBErrorMessage(0)); + EXPECT_DEATH(strcat(to, from), RightOOBWriteMessage(0)); // length of "to" is just enough. strcat(to, from + 1); @@ -1252,6 +1436,7 @@ TEST(AddressSanitizer, StrCatOOBTest) { } TEST(AddressSanitizer, StrNCatOOBTest) { + // strncat() reads strlen(to) bytes from |to| before concatenating. size_t to_size = Ident(100); char *to = MallocAndMemsetString(to_size); to[0] = '\0'; @@ -1262,26 +1447,26 @@ TEST(AddressSanitizer, StrNCatOOBTest) { strncat(to, from, from_size); from[from_size - 1] = '\0'; strncat(to, from, 2 * from_size); - // Catenating empty string is not an error. - strncat(to - 1, from, 0); + // Catenating empty string with an invalid string is still an error. + EXPECT_DEATH(strncat(to - 1, from, 0), LeftOOBAccessMessage(1)); strncat(to, from + from_size - 1, 10); // One of arguments points to not allocated memory. - EXPECT_DEATH(strncat(to - 1, from, 2), LeftOOBErrorMessage(1)); - EXPECT_DEATH(strncat(to, from - 1, 2), LeftOOBErrorMessage(1)); - EXPECT_DEATH(strncat(to + to_size, from, 2), RightOOBErrorMessage(0)); - EXPECT_DEATH(strncat(to, from + from_size, 2), RightOOBErrorMessage(0)); + EXPECT_DEATH(strncat(to - 1, from, 2), LeftOOBAccessMessage(1)); + EXPECT_DEATH(strncat(to, from - 1, 2), LeftOOBReadMessage(1)); + EXPECT_DEATH(strncat(to + to_size, from, 2), RightOOBWriteMessage(0)); + EXPECT_DEATH(strncat(to, from + from_size, 2), RightOOBReadMessage(0)); memset(from, 'z', from_size); memset(to, 'z', to_size); to[0] = '\0'; // "from" is too short. - EXPECT_DEATH(strncat(to, from, from_size + 1), RightOOBErrorMessage(0)); + EXPECT_DEATH(strncat(to, from, from_size + 1), RightOOBReadMessage(0)); // "to" is not zero-terminated. - EXPECT_DEATH(strncat(to + 1, from, 1), RightOOBErrorMessage(0)); + EXPECT_DEATH(strncat(to + 1, from, 1), RightOOBWriteMessage(0)); // "to" is too short to fit "from". to[0] = 'z'; to[to_size - from_size + 1] = '\0'; - EXPECT_DEATH(strncat(to, from, from_size - 1), RightOOBErrorMessage(0)); + EXPECT_DEATH(strncat(to, from, from_size - 1), RightOOBWriteMessage(0)); // "to" is just enough. strncat(to, from, from_size - 2); @@ -1336,7 +1521,7 @@ TEST(AddressSanitizer, StrArgsOverlapTest) { str[10] = '\0'; str[20] = '\0'; strcat(str, str + 10); - strcat(str, str + 11); + EXPECT_DEATH(strcat(str, str + 11), OverlapErrorMessage("strcat")); str[10] = '\0'; strcat(str + 11, str); EXPECT_DEATH(strcat(str, str + 9), OverlapErrorMessage("strcat")); @@ -1347,7 +1532,7 @@ TEST(AddressSanitizer, StrArgsOverlapTest) { memset(str, 'z', size); str[10] = '\0'; strncat(str, str + 10, 10); // from is empty - strncat(str, str + 11, 10); + EXPECT_DEATH(strncat(str, str + 11, 10), OverlapErrorMessage("strncat")); str[10] = '\0'; str[20] = '\0'; strncat(str + 5, str, 5); @@ -1372,10 +1557,10 @@ typedef void(*PointerToCallAtoi)(const char*); void RunAtoiOOBTest(PointerToCallAtoi Atoi) { char *array = MallocAndMemsetString(10, '1'); // Invalid pointer to the string. - EXPECT_DEATH(Atoi(array + 11), RightOOBErrorMessage(1)); - EXPECT_DEATH(Atoi(array - 1), LeftOOBErrorMessage(1)); + EXPECT_DEATH(Atoi(array + 11), RightOOBReadMessage(1)); + EXPECT_DEATH(Atoi(array - 1), LeftOOBReadMessage(1)); // Die if a buffer doesn't have terminating NULL. - EXPECT_DEATH(Atoi(array), RightOOBErrorMessage(0)); + EXPECT_DEATH(Atoi(array), RightOOBReadMessage(0)); // Make last symbol a terminating NULL or other non-digit. array[9] = '\0'; Atoi(array); @@ -1384,13 +1569,13 @@ void RunAtoiOOBTest(PointerToCallAtoi Atoi) { Atoi(array + 9); // Sometimes we need to detect overflow if no digits are found. memset(array, ' ', 10); - EXPECT_DEATH(Atoi(array), RightOOBErrorMessage(0)); + EXPECT_DEATH(Atoi(array), RightOOBReadMessage(0)); array[9] = '-'; - EXPECT_DEATH(Atoi(array), RightOOBErrorMessage(0)); - EXPECT_DEATH(Atoi(array + 9), RightOOBErrorMessage(0)); + EXPECT_DEATH(Atoi(array), RightOOBReadMessage(0)); + EXPECT_DEATH(Atoi(array + 9), RightOOBReadMessage(0)); array[8] = '-'; Atoi(array); - delete array; + free(array); } TEST(AddressSanitizer, AtoiAndFriendsOOBTest) { @@ -1414,16 +1599,16 @@ void RunStrtolOOBTest(PointerToCallStrtol Strtol) { array[1] = '2'; array[2] = '3'; // Invalid pointer to the string. - EXPECT_DEATH(Strtol(array + 3, NULL, 0), RightOOBErrorMessage(0)); - EXPECT_DEATH(Strtol(array - 1, NULL, 0), LeftOOBErrorMessage(1)); + EXPECT_DEATH(Strtol(array + 3, NULL, 0), RightOOBReadMessage(0)); + EXPECT_DEATH(Strtol(array - 1, NULL, 0), LeftOOBReadMessage(1)); // Buffer overflow if there is no terminating null (depends on base). Strtol(array, &endptr, 3); EXPECT_EQ(array + 2, endptr); - EXPECT_DEATH(Strtol(array, NULL, 0), RightOOBErrorMessage(0)); + EXPECT_DEATH(Strtol(array, NULL, 0), RightOOBReadMessage(0)); array[2] = 'z'; Strtol(array, &endptr, 35); EXPECT_EQ(array + 2, endptr); - EXPECT_DEATH(Strtol(array, NULL, 36), RightOOBErrorMessage(0)); + EXPECT_DEATH(Strtol(array, NULL, 36), RightOOBReadMessage(0)); // Add terminating zero to get rid of overflow. array[2] = '\0'; Strtol(array, NULL, 36); @@ -1432,11 +1617,11 @@ void RunStrtolOOBTest(PointerToCallStrtol Strtol) { Strtol(array + 3, NULL, 1); // Sometimes we need to detect overflow if no digits are found. array[0] = array[1] = array[2] = ' '; - EXPECT_DEATH(Strtol(array, NULL, 0), RightOOBErrorMessage(0)); + EXPECT_DEATH(Strtol(array, NULL, 0), RightOOBReadMessage(0)); array[2] = '+'; - EXPECT_DEATH(Strtol(array, NULL, 0), RightOOBErrorMessage(0)); + EXPECT_DEATH(Strtol(array, NULL, 0), RightOOBReadMessage(0)); array[2] = '-'; - EXPECT_DEATH(Strtol(array, NULL, 0), RightOOBErrorMessage(0)); + EXPECT_DEATH(Strtol(array, NULL, 0), RightOOBReadMessage(0)); array[1] = '+'; Strtol(array, NULL, 0); array[1] = array[2] = 'z'; @@ -1444,7 +1629,7 @@ void RunStrtolOOBTest(PointerToCallStrtol Strtol) { EXPECT_EQ(array, endptr); Strtol(array + 2, NULL, 0); EXPECT_EQ(array, endptr); - delete array; + free(array); } TEST(AddressSanitizer, StrtollOOBTest) { @@ -1463,7 +1648,7 @@ typedef void*(*PointerToMemSet)(void*, int, size_t); void CallMemSetByPointer(PointerToMemSet MemSet) { size_t size = Ident(100); char *array = Ident((char*)malloc(size)); - EXPECT_DEATH(MemSet(array, 0, 101), RightOOBErrorMessage(0)); + EXPECT_DEATH(MemSet(array, 0, 101), RightOOBWriteMessage(0)); free(array); } @@ -1471,7 +1656,7 @@ void CallMemTransferByPointer(PointerToMemTransfer MemTransfer) { size_t size = Ident(100); char *src = Ident((char*)malloc(size)); char *dst = Ident((char*)malloc(size)); - EXPECT_DEATH(MemTransfer(dst, src, 101), RightOOBErrorMessage(0)); + EXPECT_DEATH(MemTransfer(dst, src, 101), RightOOBWriteMessage(0)); free(src); free(dst); } @@ -1482,12 +1667,37 @@ TEST(AddressSanitizer, DISABLED_MemIntrinsicCallByPointerTest) { CallMemTransferByPointer(&memmove); } +#if defined(__linux__) && !defined(ANDROID) && !defined(__ANDROID__) +#define READ_TEST(READ_N_BYTES) \ + char *x = new char[10]; \ + int fd = open("/proc/self/stat", O_RDONLY); \ + ASSERT_GT(fd, 0); \ + EXPECT_DEATH(READ_N_BYTES, \ + ASAN_PCRE_DOTALL \ + "AddressSanitizer: heap-buffer-overflow" \ + ".* is located 0 bytes to the right of 10-byte region"); \ + close(fd); \ + delete [] x; \ + +TEST(AddressSanitizer, pread) { + READ_TEST(pread(fd, x, 15, 0)); +} + +TEST(AddressSanitizer, pread64) { + READ_TEST(pread64(fd, x, 15, 0)); +} + +TEST(AddressSanitizer, read) { + READ_TEST(read(fd, x, 15)); +} +#endif // defined(__linux__) && !defined(ANDROID) && !defined(__ANDROID__) + // This test case fails // Clang optimizes memcpy/memset calls which lead to unaligned access TEST(AddressSanitizer, DISABLED_MemIntrinsicUnalignedAccessTest) { int size = Ident(4096); char *s = Ident((char*)malloc(size)); - EXPECT_DEATH(memset(s + size - 1, 0, 2), RightOOBErrorMessage(0)); + EXPECT_DEATH(memset(s + size - 1, 0, 2), RightOOBWriteMessage(0)); free(s); } @@ -1529,7 +1739,7 @@ NOINLINE static int LargeFunction(bool do_bad_access) { TEST(AddressSanitizer, DISABLED_LargeFunctionSymbolizeTest) { int failing_line = LargeFunction(false); char expected_warning[128]; - sprintf(expected_warning, "LargeFunction.*asan_test.cc:%d", failing_line); + sprintf(expected_warning, "LargeFunction.*asan_test.*:%d", failing_line); EXPECT_DEATH(LargeFunction(true), expected_warning); } @@ -1542,19 +1752,30 @@ TEST(AddressSanitizer, DISABLED_MallocFreeUnwindAndSymbolizeTest) { "malloc_fff.*malloc_eee.*malloc_ddd"); } +static bool TryToSetThreadName(const char *name) { +#if defined(__linux__) && defined(PR_SET_NAME) + return 0 == prctl(PR_SET_NAME, (unsigned long)name, 0, 0, 0); +#else + return false; +#endif +} + void *ThreadedTestAlloc(void *a) { + EXPECT_EQ(true, TryToSetThreadName("AllocThr")); int **p = (int**)a; *p = new int; return 0; } void *ThreadedTestFree(void *a) { + EXPECT_EQ(true, TryToSetThreadName("FreeThr")); int **p = (int**)a; delete *p; return 0; } void *ThreadedTestUse(void *a) { + EXPECT_EQ(true, TryToSetThreadName("UseThr")); int **p = (int**)a; **p = 1; return 0; @@ -1563,12 +1784,12 @@ void *ThreadedTestUse(void *a) { void ThreadedTestSpawn() { pthread_t t; int *x; - pthread_create(&t, 0, ThreadedTestAlloc, &x); - pthread_join(t, 0); - pthread_create(&t, 0, ThreadedTestFree, &x); - pthread_join(t, 0); - pthread_create(&t, 0, ThreadedTestUse, &x); - pthread_join(t, 0); + PTHREAD_CREATE(&t, 0, ThreadedTestAlloc, &x); + PTHREAD_JOIN(t, 0); + PTHREAD_CREATE(&t, 0, ThreadedTestFree, &x); + PTHREAD_JOIN(t, 0); + PTHREAD_CREATE(&t, 0, ThreadedTestUse, &x); + PTHREAD_JOIN(t, 0); } TEST(AddressSanitizer, ThreadedTest) { @@ -1579,14 +1800,38 @@ TEST(AddressSanitizer, ThreadedTest) { ".*Thread T.*created"); } +void *ThreadedTestFunc(void *unused) { + // Check if prctl(PR_SET_NAME) is supported. Return if not. + if (!TryToSetThreadName("TestFunc")) + return 0; + EXPECT_DEATH(ThreadedTestSpawn(), + ASAN_PCRE_DOTALL + "WRITE .*thread T. .UseThr." + ".*freed by thread T. .FreeThr. here:" + ".*previously allocated by thread T. .AllocThr. here:" + ".*Thread T. .UseThr. created by T.*TestFunc" + ".*Thread T. .FreeThr. created by T" + ".*Thread T. .AllocThr. created by T" + ""); + return 0; +} + +TEST(AddressSanitizer, ThreadNamesTest) { + // Run ThreadedTestFunc in a separate thread because it tries to set a + // thread name and we don't want to change the main thread's name. + pthread_t t; + PTHREAD_CREATE(&t, 0, ThreadedTestFunc, 0); + PTHREAD_JOIN(t, 0); +} + #if ASAN_NEEDS_SEGV TEST(AddressSanitizer, ShadowGapTest) { -#if __WORDSIZE == 32 +#if SANITIZER_WORDSIZE == 32 char *addr = (char*)0x22000000; #else char *addr = (char*)0x0000100000080000; #endif - EXPECT_DEATH(*addr = 1, "AddressSanitizer crashed on unknown"); + EXPECT_DEATH(*addr = 1, "AddressSanitizer: SEGV on unknown"); } #endif // ASAN_NEEDS_SEGV @@ -1673,7 +1918,7 @@ TEST(AddressSanitizer, FileNameInGlobalReportTest) { static char zoo[10]; const char *p = Ident(zoo); // The file name should be present in the report. - EXPECT_DEATH(Ident(p[15]), "zoo.*asan_test.cc"); + EXPECT_DEATH(Ident(p[15]), "zoo.*asan_test."); } int *ReturnsPointerToALocalObject() { @@ -1688,7 +1933,7 @@ TEST(AddressSanitizer, LocalReferenceReturnTest) { // Call 'f' a few more times, 'p' should still be poisoned. for (int i = 0; i < 32; i++) f(); - EXPECT_DEATH(*p = 1, "AddressSanitizer stack-use-after-return"); + EXPECT_DEATH(*p = 1, "AddressSanitizer: stack-use-after-return"); EXPECT_DEATH(*p = 1, "is located.*in frame .*ReturnsPointerToALocal"); } #endif @@ -1726,10 +1971,10 @@ TEST(AddressSanitizer, ThreadedStressStackReuseTest) { const int kNumThreads = 20; pthread_t t[kNumThreads]; for (int i = 0; i < kNumThreads; i++) { - pthread_create(&t[i], 0, (void* (*)(void *x))LotsOfStackReuse, 0); + PTHREAD_CREATE(&t[i], 0, (void* (*)(void *x))LotsOfStackReuse, 0); } for (int i = 0; i < kNumThreads; i++) { - pthread_join(t[i], 0); + PTHREAD_JOIN(t[i], 0); } } @@ -1741,8 +1986,8 @@ static void *PthreadExit(void *a) { TEST(AddressSanitizer, PthreadExitTest) { pthread_t t; for (int i = 0; i < 1000; i++) { - pthread_create(&t, 0, PthreadExit, 0); - pthread_join(t, 0); + PTHREAD_CREATE(&t, 0, PthreadExit, 0); + PTHREAD_JOIN(t, 0); } } @@ -1782,7 +2027,7 @@ TEST(AddressSanitizer, LargeStructCopyTest) { *Ident(&a) = *Ident(&a); } -__attribute__((no_address_safety_analysis)) +ATTRIBUTE_NO_ADDRESS_SAFETY_ANALYSIS static void NoAddressSafety() { char *foo = new char[10]; Ident(foo)[10] = 0; @@ -1793,6 +2038,29 @@ TEST(AddressSanitizer, AttributeNoAddressSafetyTest) { Ident(NoAddressSafety)(); } +// TODO(glider): Enable this test on Mac. +// It doesn't work on Android, as calls to new/delete go through malloc/free. +#if !defined(__APPLE__) && !defined(ANDROID) && !defined(__ANDROID__) +static string MismatchStr(const string &str) { + return string("AddressSanitizer: alloc-dealloc-mismatch \\(") + str; +} + +TEST(AddressSanitizer, AllocDeallocMismatch) { + EXPECT_DEATH(free(Ident(new int)), + MismatchStr("operator new vs free")); + EXPECT_DEATH(free(Ident(new int[2])), + MismatchStr("operator new \\[\\] vs free")); + EXPECT_DEATH(delete (Ident(new int[2])), + MismatchStr("operator new \\[\\] vs operator delete")); + EXPECT_DEATH(delete (Ident((int*)malloc(2 * sizeof(int)))), + MismatchStr("malloc vs operator delete")); + EXPECT_DEATH(delete [] (Ident(new int)), + MismatchStr("operator new vs operator delete \\[\\]")); + EXPECT_DEATH(delete [] (Ident((int*)malloc(2 * sizeof(int)))), + MismatchStr("malloc vs operator delete \\[\\]")); +} +#endif + // ------------------ demo tests; run each one-by-one ------------- // e.g. --gtest_filter=*DemoOOBLeftHigh --gtest_also_run_disabled_tests TEST(AddressSanitizer, DISABLED_DemoThreadedTest) { @@ -1811,8 +2079,8 @@ TEST(AddressSanitizer, DISABLED_DemoStackTest) { TEST(AddressSanitizer, DISABLED_DemoThreadStackTest) { pthread_t t; - pthread_create(&t, 0, SimpleBugOnSTack, 0); - pthread_join(t, 0); + PTHREAD_CREATE(&t, 0, SimpleBugOnSTack, 0); + PTHREAD_JOIN(t, 0); } TEST(AddressSanitizer, DISABLED_DemoUAFLowIn) { @@ -1846,7 +2114,7 @@ TEST(AddressSanitizer, DISABLED_DemoOOBRightHigh) { } TEST(AddressSanitizer, DISABLED_DemoOOM) { - size_t size = __WORDSIZE == 64 ? (size_t)(1ULL << 40) : (0xf0000000); + size_t size = SANITIZER_WORDSIZE == 64 ? (size_t)(1ULL << 40) : (0xf0000000); printf("%p\n", malloc(size)); } @@ -1878,7 +2146,7 @@ TEST(AddressSanitizer, DISABLED_DemoTooMuchMemoryTest) { char *x = (char*)malloc(kAllocSize); memset(x, 0, kAllocSize); total_size += kAllocSize; - fprintf(stderr, "total: %ldM\n", (long)total_size >> 20); + fprintf(stderr, "total: %ldM %p\n", (long)total_size >> 20, x); } } @@ -1888,7 +2156,7 @@ TEST(AddressSanitizer, BufferOverflowAfterManyFrees) { delete [] (Ident(new char [8644])); } char *x = new char[8192]; - EXPECT_DEATH(x[Ident(8192)] = 0, "AddressSanitizer heap-buffer-overflow"); + EXPECT_DEATH(x[Ident(8192)] = 0, "AddressSanitizer: heap-buffer-overflow"); delete [] Ident(x); } @@ -1902,8 +2170,8 @@ TEST(AddressSanitizerMac, CFAllocatorDefaultDoubleFree) { void CFAllocator_DoubleFreeOnPthread() { pthread_t child; - pthread_create(&child, NULL, CFAllocatorDefaultDoubleFree, NULL); - pthread_join(child, NULL); // Shouldn't be reached. + PTHREAD_CREATE(&child, NULL, CFAllocatorDefaultDoubleFree, NULL); + PTHREAD_JOIN(child, NULL); // Shouldn't be reached. } TEST(AddressSanitizerMac, CFAllocatorDefaultDoubleFree_ChildPhread) { @@ -1928,10 +2196,10 @@ void *CFAllocatorDeallocateFromGlob(void *unused) { void CFAllocator_PassMemoryToAnotherThread() { pthread_t th1, th2; - pthread_create(&th1, NULL, CFAllocatorAllocateToGlob, NULL); - pthread_join(th1, NULL); - pthread_create(&th2, NULL, CFAllocatorDeallocateFromGlob, NULL); - pthread_join(th2, NULL); + PTHREAD_CREATE(&th1, NULL, CFAllocatorAllocateToGlob, NULL); + PTHREAD_JOIN(th1, NULL); + PTHREAD_CREATE(&th2, NULL, CFAllocatorDeallocateFromGlob, NULL); + PTHREAD_JOIN(th2, NULL); } TEST(AddressSanitizerMac, CFAllocator_PassMemoryToAnotherThread) { @@ -1958,53 +2226,56 @@ TEST(AddressSanitizerMac, DISABLED_CFAllocatorMallocZoneDoubleFree) { EXPECT_DEATH(CFAllocatorMallocZoneDoubleFree(), "attempting double-free"); } +// For libdispatch tests below we check that ASan got to the shadow byte +// legend, i.e. managed to print the thread stacks (this almost certainly +// means that the libdispatch task creation has been intercepted correctly). TEST(AddressSanitizerMac, GCDDispatchAsync) { // Make sure the whole ASan report is printed, i.e. that we don't die // on a CHECK. - EXPECT_DEATH(TestGCDDispatchAsync(), "Shadow byte and word"); + EXPECT_DEATH(TestGCDDispatchAsync(), "Shadow byte legend"); } TEST(AddressSanitizerMac, GCDDispatchSync) { // Make sure the whole ASan report is printed, i.e. that we don't die // on a CHECK. - EXPECT_DEATH(TestGCDDispatchSync(), "Shadow byte and word"); + EXPECT_DEATH(TestGCDDispatchSync(), "Shadow byte legend"); } TEST(AddressSanitizerMac, GCDReuseWqthreadsAsync) { // Make sure the whole ASan report is printed, i.e. that we don't die // on a CHECK. - EXPECT_DEATH(TestGCDReuseWqthreadsAsync(), "Shadow byte and word"); + EXPECT_DEATH(TestGCDReuseWqthreadsAsync(), "Shadow byte legend"); } TEST(AddressSanitizerMac, GCDReuseWqthreadsSync) { // Make sure the whole ASan report is printed, i.e. that we don't die // on a CHECK. - EXPECT_DEATH(TestGCDReuseWqthreadsSync(), "Shadow byte and word"); + EXPECT_DEATH(TestGCDReuseWqthreadsSync(), "Shadow byte legend"); } TEST(AddressSanitizerMac, GCDDispatchAfter) { // Make sure the whole ASan report is printed, i.e. that we don't die // on a CHECK. - EXPECT_DEATH(TestGCDDispatchAfter(), "Shadow byte and word"); + EXPECT_DEATH(TestGCDDispatchAfter(), "Shadow byte legend"); } TEST(AddressSanitizerMac, GCDSourceEvent) { // Make sure the whole ASan report is printed, i.e. that we don't die // on a CHECK. - EXPECT_DEATH(TestGCDSourceEvent(), "Shadow byte and word"); + EXPECT_DEATH(TestGCDSourceEvent(), "Shadow byte legend"); } TEST(AddressSanitizerMac, GCDSourceCancel) { // Make sure the whole ASan report is printed, i.e. that we don't die // on a CHECK. - EXPECT_DEATH(TestGCDSourceCancel(), "Shadow byte and word"); + EXPECT_DEATH(TestGCDSourceCancel(), "Shadow byte legend"); } TEST(AddressSanitizerMac, GCDGroupAsync) { // Make sure the whole ASan report is printed, i.e. that we don't die // on a CHECK. - EXPECT_DEATH(TestGCDGroupAsync(), "Shadow byte and word"); + EXPECT_DEATH(TestGCDGroupAsync(), "Shadow byte legend"); } void *MallocIntrospectionLockWorker(void *_) { @@ -2047,13 +2318,13 @@ TEST(AddressSanitizerMac, MallocIntrospectionLock) { for (iter = 0; iter < kNumIterations; iter++) { pthread_t workers[kNumWorkers], forker; for (i = 0; i < kNumWorkers; i++) { - pthread_create(&workers[i], 0, MallocIntrospectionLockWorker, 0); + PTHREAD_CREATE(&workers[i], 0, MallocIntrospectionLockWorker, 0); } - pthread_create(&forker, 0, MallocIntrospectionLockForker, 0); + PTHREAD_CREATE(&forker, 0, MallocIntrospectionLockForker, 0); for (i = 0; i < kNumWorkers; i++) { - pthread_join(workers[i], 0); + PTHREAD_JOIN(workers[i], 0); } - pthread_join(forker, 0); + PTHREAD_JOIN(forker, 0); } } @@ -2069,8 +2340,8 @@ TEST(AddressSanitizerMac, DISABLED_TSDWorkqueueTest) { pthread_t th; pthread_key_t test_key; pthread_key_create(&test_key, CallFreeOnWorkqueue); - pthread_create(&th, NULL, TSDAllocWorker, &test_key); - pthread_join(th, NULL); + PTHREAD_CREATE(&th, NULL, TSDAllocWorker, &test_key); + PTHREAD_JOIN(th, NULL); pthread_key_delete(test_key); } @@ -2092,6 +2363,19 @@ TEST(AddressSanitizerMac, NSObjectOOB) { TEST(AddressSanitizerMac, NSURLDeallocation) { TestNSURLDeallocation(); } + +// See http://code.google.com/p/address-sanitizer/issues/detail?id=109. +TEST(AddressSanitizerMac, Mstats) { + malloc_statistics_t stats1, stats2; + malloc_zone_statistics(/*all zones*/NULL, &stats1); + const size_t kMallocSize = 100000; + void *alloc = Ident(malloc(kMallocSize)); + malloc_zone_statistics(/*all zones*/NULL, &stats2); + EXPECT_GT(stats2.blocks_in_use, stats1.blocks_in_use); + EXPECT_GE(stats2.size_in_use - stats1.size_in_use, kMallocSize); + free(alloc); + // Even the default OSX allocator may not change the stats after free(). +} #endif // __APPLE__ // Test that instrumentation of stack allocations takes into account @@ -2102,11 +2386,4 @@ TEST(AddressSanitizer, LongDoubleNegativeTest) { static long double c; memcpy(Ident(&a), Ident(&b), sizeof(long double)); memcpy(Ident(&c), Ident(&b), sizeof(long double)); -}; - -int main(int argc, char **argv) { - progname = argv[0]; - testing::GTEST_FLAG(death_test_style) = "threadsafe"; - testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); } diff --git a/lib/asan/tests/asan_test.ignore b/lib/asan/tests/asan_test.ignore index 7bafa83bf62e..ea5c26099e75 100644 --- a/lib/asan/tests/asan_test.ignore +++ b/lib/asan/tests/asan_test.ignore @@ -1,2 +1,3 @@ +# blacklisted functions for instrumented ASan unit test fun:*IgnoreTest* fun:*SomeOtherFunc* diff --git a/lib/asan/tests/asan_test_config.h b/lib/asan/tests/asan_test_config.h index 6cf0e6958484..1d28e99a4b10 100644 --- a/lib/asan/tests/asan_test_config.h +++ b/lib/asan/tests/asan_test_config.h @@ -1,4 +1,4 @@ -//===-- asan_test_config.h ------------*- C++ -*-===// +//===-- asan_test_config.h --------------------------------------*- C++ -*-===// // // The LLVM Compiler Infrastructure // @@ -10,6 +10,10 @@ // This file is a part of AddressSanitizer, an address sanity checker. // //===----------------------------------------------------------------------===// +#if !defined(INCLUDED_FROM_ASAN_TEST_UTILS_H) +# error "This file should be included into asan_test_utils.h only" +#endif + #ifndef ASAN_TEST_CONFIG_H #define ASAN_TEST_CONFIG_H @@ -17,7 +21,11 @@ #include <string> #include <map> -#include "gtest/gtest.h" +#if ASAN_USE_DEJAGNU_GTEST +# include "dejagnu-gtest.h" +#else +# include "gtest/gtest.h" +#endif using std::string; using std::vector; @@ -40,7 +48,11 @@ using std::map; #endif #ifndef ASAN_LOW_MEMORY -#define ASAN_LOW_MEMORY 0 +# define ASAN_LOW_MEMORY 0 +#endif + +#ifndef ASAN_AVOID_EXPENSIVE_TESTS +# define ASAN_AVOID_EXPENSIVE_TESTS 0 #endif #define ASAN_PCRE_DOTALL "" diff --git a/lib/asan/tests/asan_break_optimization.cc b/lib/asan/tests/asan_test_main.cc index 022a9f8b8505..1746c5f4837b 100644 --- a/lib/asan/tests/asan_break_optimization.cc +++ b/lib/asan/tests/asan_test_main.cc @@ -1,4 +1,4 @@ -//===-- asan_break_optimization.cc ----------------------===// +//===-- asan_test_main.cc -------------------------------------------------===// // // The LLVM Compiler Infrastructure // @@ -10,10 +10,10 @@ // This file is a part of AddressSanitizer, an address sanity checker. // //===----------------------------------------------------------------------===// - #include "asan_test_utils.h" -// Have this function in a separate file to avoid inlining. -// (Yes, we know about cross-file inlining, but let's assume we don't use it). -extern "C" void break_optimization(void *x) { - (void)x; + +int main(int argc, char **argv) { + testing::GTEST_FLAG(death_test_style) = "threadsafe"; + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); } diff --git a/lib/asan/tests/asan_test_utils.h b/lib/asan/tests/asan_test_utils.h index fb509cc43e30..6ed9f90df906 100644 --- a/lib/asan/tests/asan_test_utils.h +++ b/lib/asan/tests/asan_test_utils.h @@ -1,4 +1,4 @@ -//===-- asan_test_utils.h ------------*- C++ -*-===// +//===-- asan_test_utils.h ---------------------------------------*- C++ -*-===// // // The LLVM Compiler Infrastructure // @@ -14,43 +14,16 @@ #ifndef ASAN_TEST_UTILS_H #define ASAN_TEST_UTILS_H -#if defined(_WIN32) -typedef unsigned __int8 uint8_t; -typedef unsigned __int16 uint16_t; -typedef unsigned __int32 uint32_t; -typedef unsigned __int64 uint64_t; -typedef __int8 int8_t; -typedef __int16 int16_t; -typedef __int32 int32_t; -typedef __int64 int64_t; -# define NOINLINE __declspec(noinline) -#else // defined(_WIN32) -# define NOINLINE __attribute__((noinline)) -#endif // defined(_WIN32) - -#if !defined(__has_feature) -#define __has_feature(x) 0 -#endif - -#ifndef __WORDSIZE -#if __LP64__ || defined(_WIN64) -#define __WORDSIZE 64 -#else -#define __WORDSIZE 32 -#endif +#if !defined(ASAN_EXTERNAL_TEST_CONFIG) +# define INCLUDED_FROM_ASAN_TEST_UTILS_H +# include "asan_test_config.h" +# undef INCLUDED_FROM_ASAN_TEST_UTILS_H #endif -// Make the compiler think that something is going on there. -extern "C" void break_optimization(void *); +#include "sanitizer_common/tests/sanitizer_test_utils.h" -// This function returns its parameter but in such a way that compiler -// can not prove it. -template<class T> -NOINLINE -static T Ident(T t) { - T ret = t; - break_optimization(&ret); - return ret; -} +// Check that pthread_create/pthread_join return success. +#define PTHREAD_CREATE(a, b, c, d) ASSERT_EQ(0, pthread_create(a, b, c, d)) +#define PTHREAD_JOIN(a, b) ASSERT_EQ(0, pthread_join(a, b)) #endif // ASAN_TEST_UTILS_H diff --git a/lib/cmpdi2.c b/lib/cmpdi2.c index c2b1f69f4fc2..52634d9c3362 100644 --- a/lib/cmpdi2.c +++ b/lib/cmpdi2.c @@ -36,3 +36,16 @@ __cmpdi2(di_int a, di_int b) return 2; return 1; } + +#ifdef __ARM_EABI__ +/* Returns: if (a < b) returns -1 +* if (a == b) returns 0 +* if (a > b) returns 1 +*/ +COMPILER_RT_ABI si_int +__aeabi_lcmp(di_int a, di_int b) +{ + return __cmpdi2(a, b) - 1; +} +#endif + diff --git a/lib/fixsfdi.c b/lib/fixsfdi.c index 8a066907e136..4f6cfdd7a5c6 100644 --- a/lib/fixsfdi.c +++ b/lib/fixsfdi.c @@ -23,7 +23,7 @@ /* seee eeee emmm mmmm mmmm mmmm mmmm mmmm */ -ARM_EABI_FNALIAS(d2lz, fixsfdi) +ARM_EABI_FNALIAS(f2lz, fixsfdi) COMPILER_RT_ABI di_int __fixsfdi(float a) diff --git a/lib/int_endianness.h b/lib/int_endianness.h index 70bd17730cb2..edb58c810e21 100644 --- a/lib/int_endianness.h +++ b/lib/int_endianness.h @@ -31,7 +31,7 @@ /* .. */ -#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) || defined(__minix) +#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__minix) #include <sys/endian.h> #if _BYTE_ORDER == _BIG_ENDIAN @@ -44,6 +44,19 @@ #endif /* *BSD */ +#if defined(__OpenBSD__) || defined(__Bitrig__) +#include <machine/endian.h> + +#if _BYTE_ORDER == _BIG_ENDIAN +#define _YUGA_LITTLE_ENDIAN 0 +#define _YUGA_BIG_ENDIAN 1 +#elif _BYTE_ORDER == _LITTLE_ENDIAN +#define _YUGA_LITTLE_ENDIAN 1 +#define _YUGA_BIG_ENDIAN 0 +#endif /* _BYTE_ORDER */ + +#endif /* OpenBSD and Bitrig. */ + /* .. */ /* Mac OSX has __BIG_ENDIAN__ or __LITTLE_ENDIAN__ automatically set by the compiler (at least with GCC) */ diff --git a/lib/interception/CMakeLists.txt b/lib/interception/CMakeLists.txt index 033b05fc1b34..ca59f2b8211e 100644 --- a/lib/interception/CMakeLists.txt +++ b/lib/interception/CMakeLists.txt @@ -6,32 +6,34 @@ set(INTERCEPTION_SOURCES interception_win.cc ) +set(MACH_OVERRIDE_SOURCES + mach_override/mach_override.c + ) + # Only add this C file if we're building on a Mac. Other source files can be # harmlessly compiled on any platform, but the C file is complained about due # to pedantic rules about empty translation units. if (APPLE) - list(APPEND INTERCEPTION_SOURCES mach_override/mach_override.c) + list(APPEND INTERCEPTION_SOURCES ${MACH_OVERRIDE_SOURCES}) + set_source_files_properties(${MACH_OVERRIDE_SOURCES} PROPERTIES COMPILE_FLAGS "-std=c99 ${INTERCEPTION_CFLAGS}") endif () -set(INTERCEPTION_CFLAGS "-fPIC -fno-exceptions -funwind-tables -fvisibility=hidden") -if (SUPPORTS_NO_VARIADIC_MACROS_FLAG) - set(INTERCEPTION_CFLAGS "${INTERCEPTION_CFLAGS} -Wno-variadic-macros") -endif () +set(INTERCEPTION_CFLAGS ${SANITIZER_COMMON_CFLAGS}) -set(INTERCEPTION_COMMON_DEFINITIONS - INTERCEPTION_HAS_EXCEPTIONS=1) - -if(CAN_TARGET_X86_64) - add_library(RTInterception.x86_64 OBJECT ${INTERCEPTION_SOURCES}) - set_property(TARGET RTInterception.x86_64 PROPERTY COMPILE_FLAGS - "${INTERCEPTION_CFLAGS} ${TARGET_X86_64_CFLAGS}") - set_property(TARGET RTInterception.x86_64 APPEND PROPERTY COMPILE_DEFINITIONS - ${INTERCEPTION_COMMON_DEFINITIONS}) -endif() -if(CAN_TARGET_I386) - add_library(RTInterception.i386 OBJECT ${INTERCEPTION_SOURCES}) - set_property(TARGET RTInterception.i386 PROPERTY COMPILE_FLAGS - "${INTERCEPTION_CFLAGS} ${TARGET_I386_CFLAGS}") - set_property(TARGET RTInterception.i386 APPEND PROPERTY COMPILE_DEFINITIONS - ${INTERCEPTION_COMMON_DEFINITIONS}) +if(APPLE) + # Build universal binary on APPLE. + add_library(RTInterception.osx OBJECT ${INTERCEPTION_SOURCES}) + set_target_compile_flags(RTInterception.osx ${INTERCEPTION_CFLAGS}) + set_target_properties(RTInterception.osx PROPERTIES + OSX_ARCHITECTURES "${SANITIZER_COMMON_SUPPORTED_ARCH}") +elseif(ANDROID) + add_library(RTInterception.arm.android OBJECT ${INTERCEPTION_SOURCES}) + set_target_compile_flags(RTInterception.arm.android + ${INTERCEPTION_CFLAGS}) +else() + # Otherwise, build separate libraries for each target. + foreach(arch ${SANITIZER_COMMON_SUPPORTED_ARCH}) + add_compiler_rt_object_library(RTInterception ${arch} + SOURCES ${INTERCEPTION_SOURCES} CFLAGS ${INTERCEPTION_CFLAGS}) + endforeach() endif() diff --git a/lib/interception/interception.h b/lib/interception/interception.h index b72bff2a6c02..030bda7cba0c 100644 --- a/lib/interception/interception.h +++ b/lib/interception/interception.h @@ -19,6 +19,17 @@ # error "Interception doesn't work on this operating system." #endif +#include "sanitizer/common_interface_defs.h" + +// These typedefs should be used only in the interceptor definitions to replace +// the standard system types (e.g. SSIZE_T instead of ssize_t) +typedef __sanitizer::uptr SIZE_T; +typedef __sanitizer::sptr SSIZE_T; +typedef __sanitizer::sptr PTRDIFF_T; +typedef __sanitizer::s64 INTMAX_T; +typedef __sanitizer::u64 OFF_T; +typedef __sanitizer::u64 OFF64_T; + // How to use this library: // 1) Include this header to define your own interceptors // (see details below). @@ -75,12 +86,22 @@ // mach_override, a handy framework for patching functions at runtime. // To avoid possible name clashes, our replacement functions have // the "wrap_" prefix on Mac. +// An alternative to function patching is to create a dylib containing a +// __DATA,__interpose section that associates library functions with their +// wrappers. When this dylib is preloaded before an executable using +// DYLD_INSERT_LIBRARIES, it routes all the calls to interposed functions done +// through stubs to the wrapper functions. Such a library is built with +// -DMAC_INTERPOSE_FUNCTIONS=1. + +#if !defined(MAC_INTERPOSE_FUNCTIONS) || !defined(__APPLE__) +# define MAC_INTERPOSE_FUNCTIONS 0 +#endif #if defined(__APPLE__) # define WRAP(x) wrap_##x # define WRAPPER_NAME(x) "wrap_"#x # define INTERCEPTOR_ATTRIBUTE -# define DECLARE_WRAPPER(ret_type, convention, func, ...) +# define DECLARE_WRAPPER(ret_type, func, ...) #elif defined(_WIN32) # if defined(_DLL) // DLL CRT # define WRAP(x) x @@ -91,63 +112,82 @@ # define WRAPPER_NAME(x) "wrap_"#x # define INTERCEPTOR_ATTRIBUTE # endif -# define DECLARE_WRAPPER(ret_type, convention, func, ...) +# define DECLARE_WRAPPER(ret_type, func, ...) #else # define WRAP(x) __interceptor_ ## x # define WRAPPER_NAME(x) "__interceptor_" #x # define INTERCEPTOR_ATTRIBUTE __attribute__((visibility("default"))) -# define DECLARE_WRAPPER(ret_type, convention, func, ...) \ - extern "C" ret_type convention func(__VA_ARGS__) \ +# define DECLARE_WRAPPER(ret_type, func, ...) \ + extern "C" ret_type func(__VA_ARGS__) \ __attribute__((weak, alias("__interceptor_" #func), visibility("default"))); #endif -#define PTR_TO_REAL(x) real_##x -#define REAL(x) __interception::PTR_TO_REAL(x) -#define FUNC_TYPE(x) x##_f - -#define DECLARE_REAL(ret_type, func, ...) \ - typedef ret_type (*FUNC_TYPE(func))(__VA_ARGS__); \ - namespace __interception { \ - extern FUNC_TYPE(func) PTR_TO_REAL(func); \ - } +#if !MAC_INTERPOSE_FUNCTIONS +# define PTR_TO_REAL(x) real_##x +# define REAL(x) __interception::PTR_TO_REAL(x) +# define FUNC_TYPE(x) x##_f + +# define DECLARE_REAL(ret_type, func, ...) \ + typedef ret_type (*FUNC_TYPE(func))(__VA_ARGS__); \ + namespace __interception { \ + extern FUNC_TYPE(func) PTR_TO_REAL(func); \ + } +#else // MAC_INTERPOSE_FUNCTIONS +# define REAL(x) x +# define DECLARE_REAL(ret_type, func, ...) \ + extern "C" ret_type func(__VA_ARGS__); +#endif // MAC_INTERPOSE_FUNCTIONS #define DECLARE_REAL_AND_INTERCEPTOR(ret_type, func, ...) \ - DECLARE_REAL(ret_type, func, ##__VA_ARGS__) \ + DECLARE_REAL(ret_type, func, __VA_ARGS__) \ extern "C" ret_type WRAP(func)(__VA_ARGS__); -// FIXME(timurrrr): We might need to add DECLARE_REAL_EX etc to support -// different calling conventions later. - -#define DEFINE_REAL_EX(ret_type, convention, func, ...) \ - typedef ret_type (convention *FUNC_TYPE(func))(__VA_ARGS__); \ - namespace __interception { \ - FUNC_TYPE(func) PTR_TO_REAL(func); \ - } - // Generally, you don't need to use DEFINE_REAL by itself, as INTERCEPTOR // macros does its job. In exceptional cases you may need to call REAL(foo) // without defining INTERCEPTOR(..., foo, ...). For example, if you override // foo with an interceptor for other function. -#define DEFAULT_CONVENTION - -#define DEFINE_REAL(ret_type, func, ...) \ - DEFINE_REAL_EX(ret_type, DEFAULT_CONVENTION, func, __VA_ARGS__) +#if !MAC_INTERPOSE_FUNCTIONS +# define DEFINE_REAL(ret_type, func, ...) \ + typedef ret_type (*FUNC_TYPE(func))(__VA_ARGS__); \ + namespace __interception { \ + FUNC_TYPE(func) PTR_TO_REAL(func); \ + } +#else +# define DEFINE_REAL(ret_type, func, ...) +#endif -#define INTERCEPTOR_EX(ret_type, convention, func, ...) \ - DEFINE_REAL_EX(ret_type, convention, func, __VA_ARGS__) \ - DECLARE_WRAPPER(ret_type, convention, func, __VA_ARGS__) \ +#define INTERCEPTOR(ret_type, func, ...) \ + DEFINE_REAL(ret_type, func, __VA_ARGS__) \ + DECLARE_WRAPPER(ret_type, func, __VA_ARGS__) \ extern "C" \ INTERCEPTOR_ATTRIBUTE \ - ret_type convention WRAP(func)(__VA_ARGS__) - -#define INTERCEPTOR(ret_type, func, ...) \ - INTERCEPTOR_EX(ret_type, DEFAULT_CONVENTION, func, __VA_ARGS__) + ret_type WRAP(func)(__VA_ARGS__) #if defined(_WIN32) # define INTERCEPTOR_WINAPI(ret_type, func, ...) \ - INTERCEPTOR_EX(ret_type, __stdcall, func, __VA_ARGS__) + typedef ret_type (__stdcall *FUNC_TYPE(func))(__VA_ARGS__); \ + namespace __interception { \ + FUNC_TYPE(func) PTR_TO_REAL(func); \ + } \ + DECLARE_WRAPPER(ret_type, func, __VA_ARGS__) \ + extern "C" \ + INTERCEPTOR_ATTRIBUTE \ + ret_type __stdcall WRAP(func)(__VA_ARGS__) #endif +// ISO C++ forbids casting between pointer-to-function and pointer-to-object, +// so we use casting via an integral type __interception::uptr, +// assuming that system is POSIX-compliant. Using other hacks seem +// challenging, as we don't even pass function type to +// INTERCEPT_FUNCTION macro, only its name. +namespace __interception { +#if defined(_WIN64) +typedef unsigned long long uptr; // NOLINT +#else +typedef unsigned long uptr; // NOLINT +#endif // _WIN64 +} // namespace __interception + #define INCLUDED_FROM_INTERCEPTION_LIB #if defined(__linux__) diff --git a/lib/interception/interception_linux.cc b/lib/interception/interception_linux.cc index 37e593323d22..009098fbd657 100644 --- a/lib/interception/interception_linux.cc +++ b/lib/interception/interception_linux.cc @@ -13,14 +13,15 @@ //===----------------------------------------------------------------------===// #ifdef __linux__ +#include "interception.h" #include <stddef.h> // for NULL #include <dlfcn.h> // for dlsym namespace __interception { -bool GetRealFunctionAddress(const char *func_name, void **func_addr, - void *real, void *wrapper) { - *func_addr = dlsym(RTLD_NEXT, func_name); +bool GetRealFunctionAddress(const char *func_name, uptr *func_addr, + uptr real, uptr wrapper) { + *func_addr = (uptr)dlsym(RTLD_NEXT, func_name); return real == wrapper; } } // namespace __interception diff --git a/lib/interception/interception_linux.h b/lib/interception/interception_linux.h index 76a29c6a99a9..dba60bf7315e 100644 --- a/lib/interception/interception_linux.h +++ b/lib/interception/interception_linux.h @@ -23,13 +23,15 @@ namespace __interception { // returns true if a function with the given name was found. -bool GetRealFunctionAddress(const char *func_name, void **func_addr, - void *real, void *wrapper); +bool GetRealFunctionAddress(const char *func_name, uptr *func_addr, + uptr real, uptr wrapper); } // namespace __interception #define INTERCEPT_FUNCTION_LINUX(func) \ - ::__interception::GetRealFunctionAddress(#func, (void**)&REAL(func), \ - (void*)&(func), (void*)&WRAP(func)) + ::__interception::GetRealFunctionAddress( \ + #func, (::__interception::uptr*)&REAL(func), \ + (::__interception::uptr)&(func), \ + (::__interception::uptr)&WRAP(func)) #endif // INTERCEPTION_LINUX_H #endif // __linux__ diff --git a/lib/interception/interception_mac.cc b/lib/interception/interception_mac.cc index cc9e4a70db8f..2c10a71210e9 100644 --- a/lib/interception/interception_mac.cc +++ b/lib/interception/interception_mac.cc @@ -14,19 +14,17 @@ #ifdef __APPLE__ -#define INCLUDED_FROM_INTERCEPTION_LIB -#include "interception_mac.h" -#undef INCLUDED_FROM_INTERCEPTION_LIB +#include "interception.h" #include "mach_override/mach_override.h" namespace __interception { -bool OverrideFunction(void *old_func, void *new_func, void **orig_old_func) { - *orig_old_func = NULL; - int res = __asan_mach_override_ptr_custom(old_func, new_func, - orig_old_func, +bool OverrideFunction(uptr old_func, uptr new_func, uptr *orig_old_func) { + *orig_old_func = 0; + int res = __asan_mach_override_ptr_custom((void*)old_func, (void*)new_func, + (void**)orig_old_func, __interception_allocate_island, __interception_deallocate_island); - return (res == 0) && (*orig_old_func != NULL); + return (res == 0) && (*orig_old_func != 0); } } // namespace __interception diff --git a/lib/interception/interception_mac.h b/lib/interception/interception_mac.h index 224d961eefe0..6e9e80817cb3 100644 --- a/lib/interception/interception_mac.h +++ b/lib/interception/interception_mac.h @@ -35,12 +35,14 @@ mach_error_t __interception_deallocate_island(void *ptr); namespace __interception { // returns true if the old function existed. -bool OverrideFunction(void *old_func, void *new_func, void **orig_old_func); +bool OverrideFunction(uptr old_func, uptr new_func, uptr *orig_old_func); } // namespace __interception # define OVERRIDE_FUNCTION_MAC(old_func, new_func) \ - ::__interception::OverrideFunction((void*)old_func, (void*)new_func, \ - (void**)&REAL(old_func)) + ::__interception::OverrideFunction( \ + (::__interception::uptr)old_func, \ + (::__interception::uptr)new_func, \ + (::__interception::uptr*)((::__interception::uptr)&REAL(old_func))) # define INTERCEPT_FUNCTION_MAC(func) OVERRIDE_FUNCTION_MAC(func, WRAP(func)) #endif // INTERCEPTION_MAC_H diff --git a/lib/interception/interception_win.cc b/lib/interception/interception_win.cc index a60c741cb3de..abbab24970cd 100644 --- a/lib/interception/interception_win.cc +++ b/lib/interception/interception_win.cc @@ -14,22 +14,23 @@ #ifdef _WIN32 +#include "interception.h" #include <windows.h> namespace __interception { -bool GetRealFunctionAddress(const char *func_name, void **func_addr) { +bool GetRealFunctionAddress(const char *func_name, uptr *func_addr) { const char *DLLS[] = { "msvcr80.dll", "msvcr90.dll", "kernel32.dll", NULL }; - *func_addr = NULL; - for (size_t i = 0; *func_addr == NULL && DLLS[i]; ++i) { - *func_addr = GetProcAddress(GetModuleHandleA(DLLS[i]), func_name); + *func_addr = 0; + for (size_t i = 0; *func_addr == 0 && DLLS[i]; ++i) { + *func_addr = (uptr)GetProcAddress(GetModuleHandleA(DLLS[i]), func_name); } - return (*func_addr != NULL); + return (*func_addr != 0); } // FIXME: internal_str* and internal_mem* functions should be moved from the @@ -55,7 +56,7 @@ static void WriteJumpInstruction(char *jmp_from, char *to) { *(ptrdiff_t*)(jmp_from + 1) = offset; } -bool OverrideFunction(void *old_func, void *new_func, void **orig_old_func) { +bool OverrideFunction(uptr old_func, uptr new_func, uptr *orig_old_func) { #ifdef _WIN64 # error OverrideFunction was not tested on x64 #endif @@ -125,20 +126,21 @@ bool OverrideFunction(void *old_func, void *new_func, void **orig_old_func) { // Now put the "jump to trampoline" instruction into the original code. DWORD old_prot, unused_prot; - if (!VirtualProtect(old_func, head, PAGE_EXECUTE_READWRITE, &old_prot)) + if (!VirtualProtect((void*)old_func, head, PAGE_EXECUTE_READWRITE, + &old_prot)) return false; // Put the needed instructions into the trampoline bytes. _memcpy(trampoline, old_bytes, head); WriteJumpInstruction(trampoline + head, old_bytes + head); - *orig_old_func = trampoline; + *orig_old_func = (uptr)trampoline; pool_used += head + 5; // Intercept the 'old_func'. WriteJumpInstruction(old_bytes, (char*)new_func); _memset(old_bytes + 5, 0xCC /* int 3 */, head - 5); - if (!VirtualProtect(old_func, head, old_prot, &unused_prot)) + if (!VirtualProtect((void*)old_func, head, old_prot, &unused_prot)) return false; // not clear if this failure bothers us. return true; diff --git a/lib/interception/interception_win.h b/lib/interception/interception_win.h index 9d1586ecb173..c64af1baffe7 100644 --- a/lib/interception/interception_win.h +++ b/lib/interception/interception_win.h @@ -23,19 +23,22 @@ namespace __interception { // returns true if a function with the given name was found. -bool GetRealFunctionAddress(const char *func_name, void **func_addr); +bool GetRealFunctionAddress(const char *func_name, uptr *func_addr); // returns true if the old function existed, false on failure. -bool OverrideFunction(void *old_func, void *new_func, void **orig_old_func); +bool OverrideFunction(uptr old_func, uptr new_func, uptr *orig_old_func); } // namespace __interception #if defined(_DLL) # define INTERCEPT_FUNCTION_WIN(func) \ - ::__interception::GetRealFunctionAddress(#func, (void**)&REAL(func)) + ::__interception::GetRealFunctionAddress( \ + #func, (::__interception::uptr*)&REAL(func)) #else # define INTERCEPT_FUNCTION_WIN(func) \ - ::__interception::OverrideFunction((void*)func, (void*)WRAP(func), \ - (void**)&REAL(func)) + ::__interception::OverrideFunction( \ + (::__interception::uptr)func, \ + (::__interception::uptr)WRAP(func), \ + (::__interception::uptr*)&REAL(func)) #endif #endif // INTERCEPTION_WIN_H diff --git a/lib/interception/mach_override/mach_override.c b/lib/interception/mach_override/mach_override.c index 499cc029b187..7511a7bebb82 100644 --- a/lib/interception/mach_override/mach_override.c +++ b/lib/interception/mach_override/mach_override.c @@ -29,6 +29,7 @@ #if defined(__ppc__) || defined(__POWERPC__) +static long kIslandTemplate[] = { 0x9001FFFC, // stw r0,-4(SP) 0x3C00DEAD, // lis r0,0xDEAD @@ -48,7 +49,8 @@ long kIslandTemplate[] = { #define kOriginalInstructionsSize 16 -char kIslandTemplate[] = { +static +unsigned char kIslandTemplate[] = { // kOriginalInstructionsSize nop instructions so that we // should have enough space to host original instructions 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, @@ -65,7 +67,8 @@ char kIslandTemplate[] = { #define kJumpAddress kOriginalInstructionsSize + 6 -char kIslandTemplate[] = { +static +unsigned char kIslandTemplate[] = { // kOriginalInstructionsSize nop instructions so that we // should have enough space to host original instructions 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, @@ -104,38 +107,41 @@ typedef struct { #pragma mark - #pragma mark (Funky Protos) - mach_error_t + + static mach_error_t allocateBranchIsland( BranchIsland **island, int allocateHigh, - void *originalFunctionAddress) __attribute__((visibility("hidden"))); + void *originalFunctionAddress); - mach_error_t + static mach_error_t freeBranchIsland( - BranchIsland *island ) __attribute__((visibility("hidden"))); + BranchIsland *island ); - mach_error_t + static mach_error_t defaultIslandMalloc( - void **ptr, size_t unused_size, void *hint) __attribute__((visibility("hidden"))); + void **ptr, size_t unused_size, void *hint); - mach_error_t + static mach_error_t defaultIslandFree( - void *ptr) __attribute__((visibility("hidden"))); + void *ptr); #if defined(__ppc__) || defined(__POWERPC__) - mach_error_t + static mach_error_t setBranchIslandTarget( BranchIsland *island, const void *branchTo, - long instruction ) __attribute__((visibility("hidden"))); + long instruction ); #endif #if defined(__i386__) || defined(__x86_64__) -mach_error_t +static mach_error_t setBranchIslandTarget_i386( BranchIsland *island, const void *branchTo, - char* instructions ) __attribute__((visibility("hidden"))); + char* instructions ); +// Can't be made static because there's no C implementation for atomic_mov64 +// on i386. void atomic_mov64( uint64_t *targetAddress, @@ -148,7 +154,7 @@ eatKnownInstructions( int *howManyEaten, char *originalInstructions, int *originalInstructionCount, - uint8_t *originalInstructionSizes ) __attribute__((visibility("hidden"))); + uint8_t *originalInstructionSizes ); static void fixupInstructions( @@ -156,7 +162,7 @@ fixupInstructions( void *escapeIsland, void *instructionsToFix, int instructionCount, - uint8_t *instructionSizes ) __attribute__((visibility("hidden"))); + uint8_t *instructionSizes ); #ifdef DEBUG_DISASM static void @@ -174,7 +180,7 @@ dump16Bytes( #pragma mark (Interface) #if defined(__i386__) || defined(__x86_64__) -mach_error_t makeIslandExecutable(void *address) { +static mach_error_t makeIslandExecutable(void *address) { mach_error_t err = err_none; vm_size_t pageSize; host_page_size( mach_host_self(), &pageSize ); @@ -189,12 +195,12 @@ mach_error_t makeIslandExecutable(void *address) { } #endif - mach_error_t + static mach_error_t defaultIslandMalloc( void **ptr, size_t unused_size, void *hint) { return allocateBranchIsland( (BranchIsland**)ptr, kAllocateHigh, hint ); } - mach_error_t + static mach_error_t defaultIslandFree( void *ptr) { return freeBranchIsland(ptr); @@ -460,7 +466,7 @@ __asan_mach_override_ptr_custom( ***************************************************************************/ - mach_error_t + static mach_error_t allocateBranchIsland( BranchIsland **island, int allocateHigh, @@ -530,7 +536,7 @@ allocateBranchIsland( ***************************************************************************/ - mach_error_t + static mach_error_t freeBranchIsland( BranchIsland *island ) { @@ -568,7 +574,7 @@ freeBranchIsland( ***************************************************************************/ #if defined(__ppc__) || defined(__POWERPC__) - mach_error_t + static mach_error_t setBranchIslandTarget( BranchIsland *island, const void *branchTo, @@ -598,7 +604,7 @@ setBranchIslandTarget( #endif #if defined(__i386__) - mach_error_t + static mach_error_t setBranchIslandTarget_i386( BranchIsland *island, const void *branchTo, @@ -622,7 +628,7 @@ setBranchIslandTarget_i386( } #elif defined(__x86_64__) -mach_error_t +static mach_error_t setBranchIslandTarget_i386( BranchIsland *island, const void *branchTo, @@ -675,7 +681,8 @@ static AsmInstructionMatch possibleInstructions[] = { { 0x4, {0xFF, 0xFF, 0xFF, 0x00}, {0x66, 0x0F, 0xEF, 0x00} }, // pxor xmm2/128, xmm1 { 0x2, {0xFF, 0xFF}, {0xDB, 0xE3} }, // fninit { 0x5, {0xFF, 0x00, 0x00, 0x00, 0x00}, {0xE8, 0x00, 0x00, 0x00, 0x00} }, // call $imm - { 0x0 } + { 0x4, {0xFF, 0xFF, 0xFF, 0x00}, {0x0F, 0xBE, 0x55, 0x00} }, // movsbl $imm(%ebp), %edx + { 0x0, {0x00}, {0x00} } }; #elif defined(__x86_64__) // TODO(glider): disassembling the "0x48, 0x89" sequences is trickier than it's done below. @@ -694,6 +701,7 @@ static AsmInstructionMatch possibleInstructions[] = { { 0x3, {0xFB, 0xFF, 0x00}, {0x48, 0x89, 0x00} }, // mov %reg, %reg { 0x3, {0xFB, 0xFF, 0x00}, {0x49, 0x89, 0x00} }, // mov %reg, %reg (REX.WB) { 0x2, {0xFF, 0x00}, {0x41, 0x00} }, // push %rXX + { 0x2, {0xFF, 0x00}, {0x84, 0x00} }, // test %rX8,%rX8 { 0x2, {0xFF, 0x00}, {0x85, 0x00} }, // test %rX,%rX { 0x2, {0xFF, 0x00}, {0x77, 0x00} }, // ja $i8 { 0x2, {0xFF, 0x00}, {0x74, 0x00} }, // je $i8 @@ -715,11 +723,15 @@ static AsmInstructionMatch possibleInstructions[] = { {0xFF, 0x25, 0x00, 0x00, 0x00, 0x00} }, // jmpq *(%rip) { 0x4, {0xFF, 0xFF, 0xFF, 0x00}, {0x66, 0x0F, 0xEF, 0x00} }, // pxor xmm2/128, xmm1 { 0x2, {0xFF, 0x00}, {0x89, 0x00} }, // mov r/m32,r32 or r/m16,r16 - { 0x3, {0xFF, 0xFF, 0xFF}, {0x49, 0x89, 0xF8} }, // mov %rdi,%r8 + { 0x3, {0xFF, 0xFF, 0xFF}, {0x49, 0x89, 0xF8} }, // mov %rdi,%r8 + { 0x4, {0xFF, 0xFF, 0xFF, 0xFF}, {0x40, 0x0F, 0xBE, 0xCE} }, // movsbl %sil,%ecx + { 0x7, {0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00}, + {0x48, 0x8D, 0x05, 0x00, 0x00, 0x00, 0x00} }, // lea $imm(%rip),%rax + { 0x3, {0xFF, 0xFF, 0xFF}, {0x0F, 0xBE, 0xCE} }, // movsbl, %dh, %ecx { 0x3, {0xFF, 0xFF, 0x00}, {0xFF, 0x77, 0x00} }, // pushq $imm(%rdi) { 0x2, {0xFF, 0xFF}, {0xDB, 0xE3} }, // fninit { 0x3, {0xFF, 0xFF, 0xFF}, {0x48, 0x85, 0xD2} }, // test %rdx,%rdx - { 0x0 } + { 0x0, {0x00}, {0x00} } }; #endif @@ -866,7 +878,7 @@ fixupInstructions( // This is critical, otherwise a near jump will likely fall outside the original function. uint32_t offset = (uintptr_t)initialOriginalFunction - (uintptr_t)escapeIsland; uint32_t jumpOffset = *(uint8_t*)((uintptr_t)instructionsToFix + 1); - *(uint8_t*)(instructionsToFix + 1) = *(uint8_t*)instructionsToFix + 0x10; + *((uint8_t*)instructionsToFix + 1) = *(uint8_t*)instructionsToFix + 0x10; *(uint8_t*)instructionsToFix = 0x0F; uint32_t *jumpOffsetPtr = (uint32_t*)((uintptr_t)instructionsToFix + 2 ); *jumpOffsetPtr = offset + jumpOffset; diff --git a/lib/lit.common.cfg b/lib/lit.common.cfg new file mode 100644 index 000000000000..428554ac77a2 --- /dev/null +++ b/lib/lit.common.cfg @@ -0,0 +1,54 @@ +# -*- Python -*- + +# Configuration file for 'lit' test runner. +# This file contains common rules for various compiler-rt testsuites. +# It is mostly copied from lit.cfg used by Clang. +import os +import platform + +# Setup test format +execute_external = (platform.system() != 'Windows' + or lit.getBashPath() not in [None, ""]) +config.test_format = lit.formats.ShTest(execute_external) + +# Setup clang binary. +clang_path = getattr(config, 'clang', None) +if (not clang_path) or (not os.path.exists(clang_path)): + lit.fatal("Can't find Clang on path %r" % clang_path) +if not lit.quiet: + lit.note("using clang: %r" % clang_path) + +# Clear some environment variables that might affect Clang. +possibly_dangerous_env_vars = ['COMPILER_PATH', 'RC_DEBUG_OPTIONS', + 'CINDEXTEST_PREAMBLE_FILE', 'LIBRARY_PATH', + 'CPATH', 'C_INCLUDE_PATH', 'CPLUS_INCLUDE_PATH', + 'OBJC_INCLUDE_PATH', 'OBJCPLUS_INCLUDE_PATH', + 'LIBCLANG_TIMING', 'LIBCLANG_OBJTRACKING', + 'LIBCLANG_LOGGING', 'LIBCLANG_BGPRIO_INDEX', + 'LIBCLANG_BGPRIO_EDIT', 'LIBCLANG_NOTHREADS', + 'LIBCLANG_RESOURCE_USAGE', + 'LIBCLANG_CODE_COMPLETION_LOGGING'] +# Clang/Win32 may refer to %INCLUDE%. vsvarsall.bat sets it. +if platform.system() != 'Windows': + possibly_dangerous_env_vars.append('INCLUDE') +for name in possibly_dangerous_env_vars: + if name in config.environment: + del config.environment[name] + +# Tweak PATH to include llvm tools dir. +llvm_tools_dir = getattr(config, 'llvm_tools_dir', None) +if (not llvm_tools_dir) or (not os.path.exists(llvm_tools_dir)): + lit.fatal("Invalid llvm_tools_dir config attribute: %r" % llvm_tools_dir) +path = os.path.pathsep.join((llvm_tools_dir, config.environment['PATH'])) +config.environment['PATH'] = path + +# Define %clang and %clangxx substitutions to use in test RUN lines. +config.substitutions.append( ("%clang ", (" " + config.clang + " ")) ) +config.substitutions.append( ("%clangxx ", (" " + config.clang + + " -ccc-cxx ")) ) + +# Use ugly construction to explicitly prohibit "clang", "clang++" etc. +# in RUN lines. +config.substitutions.append( + (' clang', """\n\n*** Do not use 'clangXXX' in tests, + instead define '%clangXXX' substitution in lit config. ***\n\n""") ) diff --git a/lib/lit.common.unit.cfg b/lib/lit.common.unit.cfg new file mode 100644 index 000000000000..8250b4a829c6 --- /dev/null +++ b/lib/lit.common.unit.cfg @@ -0,0 +1,21 @@ +# -*- Python -*- + +# Configuration file for 'lit' test runner. +# This file contains common config setup rules for unit tests in various +# compiler-rt testsuites. + +import os + +# Setup test format +build_type = getattr(config, "build_type", "Debug") +config.test_format = lit.formats.GoogleTest(build_type, "Test") + +# Setup test suffixes. +config.suffixes = [] + +# Propagate the temp directory. Windows requires this because it uses \Windows\ +# if none of these are present. +if 'TMP' in os.environ: + config.environment['TMP'] = os.environ['TMP'] +if 'TEMP' in os.environ: + config.environment['TEMP'] = os.environ['TEMP'] diff --git a/lib/msan/CMakeLists.txt b/lib/msan/CMakeLists.txt new file mode 100644 index 000000000000..bb8dbccbeba6 --- /dev/null +++ b/lib/msan/CMakeLists.txt @@ -0,0 +1,35 @@ +include_directories(..) + +# Runtime library sources and build flags. +set(MSAN_RTL_SOURCES + msan.cc + msan_allocator.cc + msan_interceptors.cc + msan_linux.cc + msan_new_delete.cc + msan_platform_limits_posix.cc + msan_report.cc + ) +set(MSAN_RTL_CFLAGS + ${SANITIZER_COMMON_CFLAGS} + -fPIE + # Prevent clang from generating libc calls. + -ffreestanding) + +# Static runtime library. +set(MSAN_RUNTIME_LIBRARIES) +add_library(clang_rt.msan-x86_64 STATIC + ${MSAN_RTL_SOURCES} + $<TARGET_OBJECTS:RTInterception.x86_64> + $<TARGET_OBJECTS:RTSanitizerCommon.x86_64> + ) +set_target_compile_flags(clang_rt.msan-x86_64 + ${MSAN_RTL_CFLAGS} ${TARGET_x86_64_CFLAGS} + ) +list(APPEND MSAN_RUNTIME_LIBRARIES clang_rt.msan-x86_64) + +add_clang_compiler_rt_libraries(${MSAN_RUNTIME_LIBRARIES}) + +if(LLVM_INCLUDE_TESTS) + add_subdirectory(tests) +endif() diff --git a/lib/msan/msan.cc b/lib/msan/msan.cc new file mode 100644 index 000000000000..670213f011c7 --- /dev/null +++ b/lib/msan/msan.cc @@ -0,0 +1,420 @@ +//===-- msan.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 MemorySanitizer. +// +// MemorySanitizer runtime. +//===----------------------------------------------------------------------===// + +#include "msan.h" +#include "sanitizer_common/sanitizer_atomic.h" +#include "sanitizer_common/sanitizer_common.h" +#include "sanitizer_common/sanitizer_flags.h" +#include "sanitizer_common/sanitizer_libc.h" +#include "sanitizer_common/sanitizer_procmaps.h" +#include "sanitizer_common/sanitizer_stacktrace.h" +#include "sanitizer_common/sanitizer_symbolizer.h" + +#include "interception/interception.h" + +// ACHTUNG! No system header includes in this file. + +using namespace __sanitizer; + +// Globals. +static THREADLOCAL int msan_expect_umr = 0; +static THREADLOCAL int msan_expected_umr_found = 0; + +static int msan_running_under_dr = 0; + +SANITIZER_INTERFACE_ATTRIBUTE +THREADLOCAL u64 __msan_param_tls[kMsanParamTlsSizeInWords]; + +SANITIZER_INTERFACE_ATTRIBUTE +THREADLOCAL u32 __msan_param_origin_tls[kMsanParamTlsSizeInWords]; + +SANITIZER_INTERFACE_ATTRIBUTE +THREADLOCAL u64 __msan_retval_tls[kMsanRetvalTlsSizeInWords]; + +SANITIZER_INTERFACE_ATTRIBUTE +THREADLOCAL u32 __msan_retval_origin_tls; + +SANITIZER_INTERFACE_ATTRIBUTE +THREADLOCAL u64 __msan_va_arg_tls[kMsanParamTlsSizeInWords]; + +SANITIZER_INTERFACE_ATTRIBUTE +THREADLOCAL u64 __msan_va_arg_overflow_size_tls; + +SANITIZER_INTERFACE_ATTRIBUTE +THREADLOCAL u32 __msan_origin_tls; + +static THREADLOCAL struct { + uptr stack_top, stack_bottom; +} __msan_stack_bounds; + +extern const int __msan_track_origins; +int __msan_get_track_origins() { + return __msan_track_origins; +} + +namespace __msan { + +static bool IsRunningUnderDr() { + bool result = false; + MemoryMappingLayout proc_maps; + const sptr kBufSize = 4095; + char *filename = (char*)MmapOrDie(kBufSize, __FUNCTION__); + while (proc_maps.Next(/* start */0, /* end */0, /* file_offset */0, + filename, kBufSize)) { + if (internal_strstr(filename, "libdynamorio") != 0) { + result = true; + break; + } + } + UnmapOrDie(filename, kBufSize); + return result; +} + +static Flags msan_flags; + +Flags *flags() { + return &msan_flags; +} + +int msan_inited = 0; +bool msan_init_is_running; + +int msan_report_count = 0; + +// Array of stack origins. +// FIXME: make it resizable. +static const uptr kNumStackOriginDescrs = 1024 * 1024; +static const char *StackOriginDescr[kNumStackOriginDescrs]; +static atomic_uint32_t NumStackOriginDescrs; + +static void ParseFlagsFromString(Flags *f, const char *str) { + ParseFlag(str, &f->poison_heap_with_zeroes, "poison_heap_with_zeroes"); + ParseFlag(str, &f->poison_stack_with_zeroes, "poison_stack_with_zeroes"); + ParseFlag(str, &f->poison_in_malloc, "poison_in_malloc"); + ParseFlag(str, &f->exit_code, "exit_code"); + if (f->exit_code < 0 || f->exit_code > 127) { + Printf("Exit code not in [0, 128) range: %d\n", f->exit_code); + f->exit_code = 1; + Die(); + } + ParseFlag(str, &f->num_callers, "num_callers"); + ParseFlag(str, &f->report_umrs, "report_umrs"); + ParseFlag(str, &f->verbosity, "verbosity"); +} + +static void InitializeFlags(Flags *f, const char *options) { + internal_memset(f, 0, sizeof(*f)); + + f->poison_heap_with_zeroes = false; + f->poison_stack_with_zeroes = false; + f->poison_in_malloc = true; + f->exit_code = 77; + f->num_callers = 20; + f->report_umrs = true; + f->verbosity = 0; + + ParseFlagsFromString(f, options); +} + +static void GetCurrentStackBounds(uptr *stack_top, uptr *stack_bottom) { + if (__msan_stack_bounds.stack_top == 0) { + // Break recursion (GetStackTrace -> GetThreadStackTopAndBottom -> + // realloc -> GetStackTrace). + __msan_stack_bounds.stack_top = __msan_stack_bounds.stack_bottom = 1; + GetThreadStackTopAndBottom(/* at_initialization */false, + &__msan_stack_bounds.stack_top, + &__msan_stack_bounds.stack_bottom); + } + *stack_top = __msan_stack_bounds.stack_top; + *stack_bottom = __msan_stack_bounds.stack_bottom; +} + +void GetStackTrace(StackTrace *stack, uptr max_s, uptr pc, uptr bp) { + uptr stack_top, stack_bottom; + GetCurrentStackBounds(&stack_top, &stack_bottom); + stack->size = 0; + stack->trace[0] = pc; + stack->max_size = max_s; + stack->FastUnwindStack(pc, bp, stack_top, stack_bottom); +} + +void PrintWarning(uptr pc, uptr bp) { + PrintWarningWithOrigin(pc, bp, __msan_origin_tls); +} + +bool OriginIsValid(u32 origin) { + return origin != 0 && origin != (u32)-1; +} + +void PrintWarningWithOrigin(uptr pc, uptr bp, u32 origin) { + if (msan_expect_umr) { + // Printf("Expected UMR\n"); + __msan_origin_tls = origin; + msan_expected_umr_found = 1; + return; + } + + ++msan_report_count; + + StackTrace stack; + GetStackTrace(&stack, kStackTraceMax, pc, bp); + + u32 report_origin = + (__msan_track_origins && OriginIsValid(origin)) ? origin : 0; + ReportUMR(&stack, report_origin); + + if (__msan_track_origins && !OriginIsValid(origin)) { + Printf(" ORIGIN: invalid (%x). Might be a bug in MemorySanitizer, " + "please report to MemorySanitizer developers.\n", + origin); + } +} + +} // namespace __msan + +// Interface. + +using namespace __msan; + +void __msan_warning() { + GET_CALLER_PC_BP_SP; + (void)sp; + PrintWarning(pc, bp); +} + +void __msan_warning_noreturn() { + GET_CALLER_PC_BP_SP; + (void)sp; + PrintWarning(pc, bp); + Printf("Exiting\n"); + Die(); +} + +void __msan_init() { + if (msan_inited) return; + msan_init_is_running = 1; + + InstallAtExitHandler(); + SetDieCallback(MsanDie); + InitializeInterceptors(); + + ReplaceOperatorsNewAndDelete(); + if (StackSizeIsUnlimited()) { + if (flags()->verbosity) + Printf("Unlimited stack, doing reexec\n"); + // A reasonably large stack size. It is bigger than the usual 8Mb, because, + // well, the program could have been run with unlimited stack for a reason. + SetStackSizeLimitInBytes(32 * 1024 * 1024); + ReExec(); + } + const char *msan_options = GetEnv("MSAN_OPTIONS"); + InitializeFlags(&msan_flags, msan_options); + if (flags()->verbosity) + Printf("MSAN_OPTIONS: %s\n", msan_options ? msan_options : "<empty>"); + msan_running_under_dr = IsRunningUnderDr(); + __msan_clear_on_return(); + if (__msan_track_origins && flags()->verbosity > 0) + Printf("msan_track_origins\n"); + if (!InitShadow(/* prot1 */false, /* prot2 */true, /* map_shadow */true, + __msan_track_origins)) { + // FIXME: prot1 = false is only required when running under DR. + Printf("FATAL: MemorySanitizer can not mmap the shadow memory.\n"); + Printf("FATAL: Make sure to compile with -fPIE and to link with -pie.\n"); + Printf("FATAL: Disabling ASLR is known to cause this error.\n"); + Printf("FATAL: If running under GDB, try " + "'set disable-randomization off'.\n"); + DumpProcessMap(); + Die(); + } + + InstallTrapHandler(); + + const char *external_symbolizer = GetEnv("MSAN_SYMBOLIZER_PATH"); + if (external_symbolizer && external_symbolizer[0]) { + CHECK(InitializeExternalSymbolizer(external_symbolizer)); + } + + GetThreadStackTopAndBottom(/* at_initialization */true, + &__msan_stack_bounds.stack_top, + &__msan_stack_bounds.stack_bottom); + if (flags()->verbosity) + Printf("MemorySanitizer init done\n"); + msan_init_is_running = 0; + msan_inited = 1; +} + +void __msan_set_exit_code(int exit_code) { + flags()->exit_code = exit_code; +} + +void __msan_set_expect_umr(int expect_umr) { + if (expect_umr) { + msan_expected_umr_found = 0; + } else if (!msan_expected_umr_found) { + GET_CALLER_PC_BP_SP; + (void)sp; + StackTrace stack; + GetStackTrace(&stack, kStackTraceMax, pc, bp); + ReportExpectedUMRNotFound(&stack); + Die(); + } + msan_expect_umr = expect_umr; +} + +void __msan_print_shadow(const void *x, uptr size) { + unsigned char *s = (unsigned char*)MEM_TO_SHADOW(x); + u32 *o = (u32*)MEM_TO_ORIGIN(x); + for (uptr i = 0; i < size; i++) { + Printf("%x%x ", s[i] >> 4, s[i] & 0xf); + } + Printf("\n"); + if (__msan_track_origins) { + for (uptr i = 0; i < size / 4; i++) { + Printf(" o: %x ", o[i]); + } + Printf("\n"); + } +} + +void __msan_print_param_shadow() { + for (int i = 0; i < 16; i++) { + Printf("#%d:%zx ", i, __msan_param_tls[i]); + } + Printf("\n"); +} + +sptr __msan_test_shadow(const void *x, uptr size) { + unsigned char *s = (unsigned char*)MEM_TO_SHADOW((uptr)x); + for (uptr i = 0; i < size; ++i) + if (s[i]) + return i; + return -1; +} + +int __msan_set_poison_in_malloc(int do_poison) { + int old = flags()->poison_in_malloc; + flags()->poison_in_malloc = do_poison; + return old; +} + +void __msan_break_optimization(void *x) { } + +int __msan_has_dynamic_component() { + return msan_running_under_dr; +} + +NOINLINE +void __msan_clear_on_return() { + __msan_param_tls[0] = 0; +} + +static void* get_tls_base() { + u64 p; + asm("mov %%fs:0, %0" + : "=r"(p) ::); + return (void*)p; +} + +int __msan_get_retval_tls_offset() { + // volatile here is needed to avoid UB, because the compiler thinks that we + // are doing address arithmetics on unrelated pointers, and takes some + // shortcuts + volatile sptr retval_tls_p = (sptr)&__msan_retval_tls; + volatile sptr tls_base_p = (sptr)get_tls_base(); + return retval_tls_p - tls_base_p; +} + +int __msan_get_param_tls_offset() { + // volatile here is needed to avoid UB, because the compiler thinks that we + // are doing address arithmetics on unrelated pointers, and takes some + // shortcuts + volatile sptr param_tls_p = (sptr)&__msan_param_tls; + volatile sptr tls_base_p = (sptr)get_tls_base(); + return param_tls_p - tls_base_p; +} + +void __msan_partial_poison(void* data, void* shadow, uptr size) { + internal_memcpy((void*)MEM_TO_SHADOW((uptr)data), shadow, size); +} + +void __msan_load_unpoisoned(void *src, uptr size, void *dst) { + internal_memcpy(dst, src, size); + __msan_unpoison(dst, size); +} + +void __msan_set_origin(void *a, uptr size, u32 origin) { + // Origin mapping is 4 bytes per 4 bytes of application memory. + // Here we extend the range such that its left and right bounds are both + // 4 byte aligned. + if (!__msan_track_origins) return; + uptr x = MEM_TO_ORIGIN((uptr)a); + uptr beg = x & ~3UL; // align down. + uptr end = (x + size + 3) & ~3UL; // align up. + u64 origin64 = ((u64)origin << 32) | origin; + // This is like memset, but the value is 32-bit. We unroll by 2 two write + // 64-bits at once. May want to unroll further to get 128-bit stores. + if (beg & 7ULL) { + *(u32*)beg = origin; + beg += 4; + } + for (uptr addr = beg; addr < (end & ~7UL); addr += 8) + *(u64*)addr = origin64; + if (end & 7ULL) + *(u32*)(end - 4) = origin; +} + +// 'descr' is created at compile time and contains '----' in the beginning. +// When we see descr for the first time we replace '----' with a uniq id +// and set the origin to (id | (31-th bit)). +void __msan_set_alloca_origin(void *a, uptr size, const char *descr) { + static const u32 dash = '-'; + static const u32 first_timer = + dash + (dash << 8) + (dash << 16) + (dash << 24); + u32 *id_ptr = (u32*)descr; + bool print = false; // internal_strstr(descr + 4, "AllocaTOTest") != 0; + u32 id = *id_ptr; + if (id == first_timer) { + id = atomic_fetch_add(&NumStackOriginDescrs, + 1, memory_order_relaxed); + *id_ptr = id; + CHECK_LT(id, kNumStackOriginDescrs); + StackOriginDescr[id] = descr + 4; + if (print) + Printf("First time: id=%d %s \n", id, descr + 4); + } + id |= 1U << 31; + if (print) + Printf("__msan_set_alloca_origin: descr=%s id=%x\n", descr + 4, id); + __msan_set_origin(a, size, id); +} + +const char *__msan_get_origin_descr_if_stack(u32 id) { + if ((id >> 31) == 0) return 0; + id &= (1U << 31) - 1; + CHECK_LT(id, kNumStackOriginDescrs); + return StackOriginDescr[id]; +} + + +u32 __msan_get_origin(void *a) { + if (!__msan_track_origins) return 0; + uptr x = (uptr)a; + uptr aligned = x & ~3ULL; + uptr origin_ptr = MEM_TO_ORIGIN(aligned); + return *(u32*)origin_ptr; +} + +u32 __msan_get_origin_tls() { + return __msan_origin_tls; +} diff --git a/lib/msan/msan.h b/lib/msan/msan.h new file mode 100644 index 000000000000..99d9a90d2dc7 --- /dev/null +++ b/lib/msan/msan.h @@ -0,0 +1,68 @@ +//===-- msan.h --------------------------------------------------*- C++ -*-===// +// +// 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 MemorySanitizer. +// +// Private MSan header. +//===----------------------------------------------------------------------===// + +#ifndef MSAN_H +#define MSAN_H + +#include "sanitizer_common/sanitizer_internal_defs.h" +#include "sanitizer_common/sanitizer_stacktrace.h" +#include "sanitizer/msan_interface.h" +#include "msan_flags.h" + +#define MEM_TO_SHADOW(mem) (((uptr)mem) & ~0x400000000000ULL) +#define MEM_TO_ORIGIN(mem) (MEM_TO_SHADOW(mem) + 0x200000000000ULL) +#define MEM_IS_APP(mem) ((uptr)mem >= 0x600000000000ULL) +#define MEM_IS_SHADOW(mem) ((uptr)mem >= 0x200000000000ULL && \ + (uptr)mem <= 0x400000000000ULL) + +const int kMsanParamTlsSizeInWords = 100; +const int kMsanRetvalTlsSizeInWords = 100; + +namespace __msan { +extern int msan_inited; +extern bool msan_init_is_running; +extern int msan_report_count; + +bool ProtectRange(uptr beg, uptr end); +bool InitShadow(bool prot1, bool prot2, bool map_shadow, bool init_origins); +char *GetProcSelfMaps(); +void InitializeInterceptors(); + +void *MsanReallocate(StackTrace *stack, void *oldp, uptr size, + uptr alignment, bool zeroise); +void MsanDeallocate(void *ptr); +void InstallTrapHandler(); +void InstallAtExitHandler(); +void ReplaceOperatorsNewAndDelete(); + +void MsanDie(); +void PrintWarning(uptr pc, uptr bp); +void PrintWarningWithOrigin(uptr pc, uptr bp, u32 origin); + +void GetStackTrace(StackTrace *stack, uptr max_s, uptr pc, uptr bp); + +void ReportUMR(StackTrace *stack, u32 origin); +void ReportExpectedUMRNotFound(StackTrace *stack); +void ReportAtExitStatistics(); + +#define GET_MALLOC_STACK_TRACE \ + StackTrace stack; \ + stack.size = 0; \ + if (__msan_get_track_origins() && msan_inited) \ + GetStackTrace(&stack, flags()->num_callers, \ + StackTrace::GetCurrentPc(), GET_CURRENT_FRAME()) + +} // namespace __msan + +#endif // MSAN_H diff --git a/lib/msan/msan_allocator.cc b/lib/msan/msan_allocator.cc new file mode 100644 index 000000000000..7435843ce61e --- /dev/null +++ b/lib/msan/msan_allocator.cc @@ -0,0 +1,107 @@ +//===-- msan_allocator.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 MemorySanitizer. +// +// MemorySanitizer allocator. +//===----------------------------------------------------------------------===// + +#include "sanitizer_common/sanitizer_allocator.h" +#include "sanitizer_common/sanitizer_stackdepot.h" +#include "msan.h" + +namespace __msan { + +struct Metadata { + uptr requested_size; +}; + +static const uptr kAllocatorSpace = 0x600000000000ULL; +static const uptr kAllocatorSize = 0x80000000000; // 8T. +static const uptr kMetadataSize = sizeof(Metadata); + +typedef SizeClassAllocator64<kAllocatorSpace, kAllocatorSize, kMetadataSize, + DefaultSizeClassMap> PrimaryAllocator; +typedef SizeClassAllocatorLocalCache<PrimaryAllocator> AllocatorCache; +typedef LargeMmapAllocator<> SecondaryAllocator; +typedef CombinedAllocator<PrimaryAllocator, AllocatorCache, + SecondaryAllocator> Allocator; + +static THREADLOCAL AllocatorCache cache; +static Allocator allocator; + +static int inited = 0; + +static inline void Init() { + if (inited) return; + __msan_init(); + inited = true; // this must happen before any threads are created. + allocator.Init(); +} + +static void *MsanAllocate(StackTrace *stack, uptr size, + uptr alignment, bool zeroise) { + Init(); + void *res = allocator.Allocate(&cache, size, alignment, false); + Metadata *meta = reinterpret_cast<Metadata*>(allocator.GetMetaData(res)); + meta->requested_size = size; + if (zeroise) + __msan_clear_and_unpoison(res, size); + else if (flags()->poison_in_malloc) + __msan_poison(res, size); + if (__msan_get_track_origins()) { + u32 stack_id = StackDepotPut(stack->trace, stack->size); + CHECK(stack_id); + CHECK_EQ((stack_id >> 31), 0); // Higher bit is occupied by stack origins. + __msan_set_origin(res, size, stack_id); + } + return res; +} + +void MsanDeallocate(void *p) { + CHECK(p); + Init(); + Metadata *meta = reinterpret_cast<Metadata*>(allocator.GetMetaData(p)); + uptr size = meta->requested_size; + // This memory will not be reused by anyone else, so we are free to keep it + // poisoned. + __msan_poison(p, size); + if (__msan_get_track_origins()) + __msan_set_origin(p, size, -1); + allocator.Deallocate(&cache, p); +} + +void *MsanReallocate(StackTrace *stack, void *old_p, uptr new_size, + uptr alignment, bool zeroise) { + if (!old_p) + return MsanAllocate(stack, new_size, alignment, zeroise); + if (!new_size) { + MsanDeallocate(old_p); + return 0; + } + Metadata *meta = reinterpret_cast<Metadata*>(allocator.GetMetaData(old_p)); + uptr old_size = meta->requested_size; + uptr actually_allocated_size = allocator.GetActuallyAllocatedSize(old_p); + if (new_size <= actually_allocated_size) { + // We are not reallocating here. + meta->requested_size = new_size; + if (new_size > old_size) + __msan_poison((char*)old_p + old_size, new_size - old_size); + return old_p; + } + uptr memcpy_size = Min(new_size, old_size); + void *new_p = MsanAllocate(stack, new_size, alignment, zeroise); + // Printf("realloc: old_size %zd new_size %zd\n", old_size, new_size); + if (new_p) + __msan_memcpy(new_p, old_p, memcpy_size); + MsanDeallocate(old_p); + return new_p; +} + +} // namespace __msan diff --git a/lib/msan/msan_flags.h b/lib/msan/msan_flags.h new file mode 100644 index 000000000000..a85fc57253c9 --- /dev/null +++ b/lib/msan/msan_flags.h @@ -0,0 +1,34 @@ +//===-- msan_flags.h --------------------------------------------*- C++ -*-===// +// +// 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 MemorySanitizer. +// +// MemorySanitizer allocator. +//===----------------------------------------------------------------------===// +#ifndef MSAN_FLAGS_H +#define MSAN_FLAGS_H + +namespace __msan { + +// Flags. +struct Flags { + int exit_code; + int num_callers; + int verbosity; + bool poison_heap_with_zeroes; // default: false + bool poison_stack_with_zeroes; // default: false + bool poison_in_malloc; // default: true + bool report_umrs; +}; + +Flags *flags(); + +} // namespace __msan + +#endif // MSAN_FLAGS_H diff --git a/lib/msan/msan_interceptors.cc b/lib/msan/msan_interceptors.cc new file mode 100644 index 000000000000..462920413d4e --- /dev/null +++ b/lib/msan/msan_interceptors.cc @@ -0,0 +1,942 @@ +//===-- msan_interceptors.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 MemorySanitizer. +// +// Interceptors for standard library functions. +// +// FIXME: move as many interceptors as possible into +// sanitizer_common/sanitizer_common_interceptors.h +//===----------------------------------------------------------------------===// + +#include "interception/interception.h" +#include "msan.h" +#include "msan_platform_limits_posix.h" +#include "sanitizer_common/sanitizer_common.h" +#include "sanitizer_common/sanitizer_libc.h" + +#include <stdarg.h> +// ACHTUNG! No other system header includes in this file. +// Ideally, we should get rid of stdarg.h as well. + +using namespace __msan; + +#define ENSURE_MSAN_INITED() do { \ + CHECK(!msan_init_is_running); \ + if (!msan_inited) { \ + __msan_init(); \ + } \ +} while (0) + +#define CHECK_UNPOISONED(x, n) \ + do { \ + sptr offset = __msan_test_shadow(x, n); \ + if (offset >= 0 && flags()->report_umrs) { \ + GET_CALLER_PC_BP_SP; \ + (void)sp; \ + Printf("UMR in %s at offset %d inside [%p, +%d) \n", \ + __FUNCTION__, offset, x, n); \ + __msan::PrintWarningWithOrigin( \ + pc, bp, __msan_get_origin((char*)x + offset)); \ + } \ + } while (0) + +static void *fast_memset(void *ptr, int c, SIZE_T n); +static void *fast_memcpy(void *dst, const void *src, SIZE_T n); + +INTERCEPTOR(SIZE_T, fread, void *ptr, SIZE_T size, SIZE_T nmemb, void *file) { + ENSURE_MSAN_INITED(); + SIZE_T res = REAL(fread)(ptr, size, nmemb, file); + if (res > 0) + __msan_unpoison(ptr, res *size); + return res; +} + +INTERCEPTOR(SIZE_T, fread_unlocked, void *ptr, SIZE_T size, SIZE_T nmemb, + void *file) { + ENSURE_MSAN_INITED(); + SIZE_T res = REAL(fread_unlocked)(ptr, size, nmemb, file); + if (res > 0) + __msan_unpoison(ptr, res *size); + return res; +} + +INTERCEPTOR(SSIZE_T, readlink, const char *path, char *buf, SIZE_T bufsiz) { + ENSURE_MSAN_INITED(); + SSIZE_T res = REAL(readlink)(path, buf, bufsiz); + if (res > 0) + __msan_unpoison(buf, res); + return res; +} + +INTERCEPTOR(void *, readdir, void *a) { + ENSURE_MSAN_INITED(); + void *res = REAL(readdir)(a); + __msan_unpoison(res, __msan::struct_dirent_sz); + return res; +} + +INTERCEPTOR(void *, memcpy, void *dest, const void *src, SIZE_T n) { + return __msan_memcpy(dest, src, n); +} + +INTERCEPTOR(void *, memmove, void *dest, const void *src, SIZE_T n) { + return __msan_memmove(dest, src, n); +} + +INTERCEPTOR(void *, memset, void *s, int c, SIZE_T n) { + return __msan_memset(s, c, n); +} + +INTERCEPTOR(int, posix_memalign, void **memptr, SIZE_T alignment, SIZE_T size) { + GET_MALLOC_STACK_TRACE; + CHECK_EQ(alignment & (alignment - 1), 0); + *memptr = MsanReallocate(&stack, 0, size, alignment, false); + CHECK_NE(memptr, 0); + return 0; +} + +INTERCEPTOR(void, free, void *ptr) { + ENSURE_MSAN_INITED(); + if (ptr == 0) return; + MsanDeallocate(ptr); +} + +INTERCEPTOR(SIZE_T, strlen, const char *s) { + ENSURE_MSAN_INITED(); + SIZE_T res = REAL(strlen)(s); + CHECK_UNPOISONED(s, res + 1); + return res; +} + +INTERCEPTOR(SIZE_T, strnlen, const char *s, SIZE_T n) { + ENSURE_MSAN_INITED(); + SIZE_T res = REAL(strnlen)(s, n); + SIZE_T scan_size = (res == n) ? res : res + 1; + CHECK_UNPOISONED(s, scan_size); + return res; +} + +// FIXME: Add stricter shadow checks in str* interceptors (ex.: strcpy should +// check the shadow of the terminating \0 byte). + +INTERCEPTOR(char *, strcpy, char *dest, const char *src) { // NOLINT + ENSURE_MSAN_INITED(); + SIZE_T n = REAL(strlen)(src); + char *res = REAL(strcpy)(dest, src); // NOLINT + __msan_copy_poison(dest, src, n + 1); + return res; +} + +INTERCEPTOR(char *, strncpy, char *dest, const char *src, SIZE_T n) { // NOLINT + ENSURE_MSAN_INITED(); + SIZE_T copy_size = REAL(strnlen)(src, n); + if (copy_size < n) + copy_size++; // trailing \0 + char *res = REAL(strncpy)(dest, src, n); // NOLINT + __msan_copy_poison(dest, src, copy_size); + return res; +} + +INTERCEPTOR(char *, strdup, char *src) { + ENSURE_MSAN_INITED(); + SIZE_T n = REAL(strlen)(src); + char *res = REAL(strdup)(src); + __msan_copy_poison(res, src, n + 1); + return res; +} + +INTERCEPTOR(char *, gcvt, double number, SIZE_T ndigit, char *buf) { + ENSURE_MSAN_INITED(); + char *res = REAL(gcvt)(number, ndigit, buf); + // DynamoRio tool will take care of unpoisoning gcvt result for us. + if (!__msan_has_dynamic_component()) { + SIZE_T n = REAL(strlen)(buf); + __msan_unpoison(buf, n + 1); + } + return res; +} + +INTERCEPTOR(char *, strcat, char *dest, const char *src) { // NOLINT + ENSURE_MSAN_INITED(); + SIZE_T src_size = REAL(strlen)(src); + SIZE_T dest_size = REAL(strlen)(dest); + char *res = REAL(strcat)(dest, src); // NOLINT + __msan_copy_poison(dest + dest_size, src, src_size + 1); + return res; +} + +INTERCEPTOR(char *, strncat, char *dest, const char *src, SIZE_T n) { // NOLINT + ENSURE_MSAN_INITED(); + SIZE_T dest_size = REAL(strlen)(dest); + SIZE_T copy_size = REAL(strlen)(src); + if (copy_size < n) + copy_size++; // trailing \0 + char *res = REAL(strncat)(dest, src, n); // NOLINT + __msan_copy_poison(dest + dest_size, src, copy_size); + return res; +} + +INTERCEPTOR(long, strtol, const char *nptr, char **endptr, // NOLINT + int base) { + ENSURE_MSAN_INITED(); + long res = REAL(strtol)(nptr, endptr, base); // NOLINT + if (!__msan_has_dynamic_component()) { + __msan_unpoison(endptr, sizeof(*endptr)); + } + return res; +} + +INTERCEPTOR(long long, strtoll, const char *nptr, char **endptr, // NOLINT + int base) { + ENSURE_MSAN_INITED(); + long res = REAL(strtoll)(nptr, endptr, base); //NOLINT + if (!__msan_has_dynamic_component()) { + __msan_unpoison(endptr, sizeof(*endptr)); + } + return res; +} + +INTERCEPTOR(unsigned long, strtoul, const char *nptr, char **endptr, // NOLINT + int base) { + ENSURE_MSAN_INITED(); + unsigned long res = REAL(strtoul)(nptr, endptr, base); // NOLINT + if (!__msan_has_dynamic_component()) { + __msan_unpoison(endptr, sizeof(*endptr)); + } + return res; +} + +INTERCEPTOR(unsigned long long, strtoull, const char *nptr, // NOLINT + char **endptr, int base) { + ENSURE_MSAN_INITED(); + unsigned long res = REAL(strtoull)(nptr, endptr, base); // NOLINT + if (!__msan_has_dynamic_component()) { + __msan_unpoison(endptr, sizeof(*endptr)); + } + return res; +} + +INTERCEPTOR(double, strtod, const char *nptr, char **endptr) { // NOLINT + ENSURE_MSAN_INITED(); + double res = REAL(strtod)(nptr, endptr); // NOLINT + if (!__msan_has_dynamic_component()) { + __msan_unpoison(endptr, sizeof(*endptr)); + } + return res; +} + +INTERCEPTOR(float, strtof, const char *nptr, char **endptr) { // NOLINT + ENSURE_MSAN_INITED(); + float res = REAL(strtof)(nptr, endptr); // NOLINT + if (!__msan_has_dynamic_component()) { + __msan_unpoison(endptr, sizeof(*endptr)); + } + return res; +} + +INTERCEPTOR(long double, strtold, const char *nptr, char **endptr) { // NOLINT + ENSURE_MSAN_INITED(); + long double res = REAL(strtold)(nptr, endptr); // NOLINT + if (!__msan_has_dynamic_component()) { + __msan_unpoison(endptr, sizeof(*endptr)); + } + return res; +} + +INTERCEPTOR(int, vsnprintf, char *str, uptr size, + const char *format, va_list ap) { + ENSURE_MSAN_INITED(); + int res = REAL(vsnprintf)(str, size, format, ap); + if (!__msan_has_dynamic_component()) { + __msan_unpoison(str, res + 1); + } + return res; +} + +INTERCEPTOR(int, vsprintf, char *str, const char *format, va_list ap) { + ENSURE_MSAN_INITED(); + int res = REAL(vsprintf)(str, format, ap); + if (!__msan_has_dynamic_component()) { + __msan_unpoison(str, res + 1); + } + return res; +} + +INTERCEPTOR(int, vswprintf, void *str, uptr size, void *format, va_list ap) { + ENSURE_MSAN_INITED(); + int res = REAL(vswprintf)(str, size, format, ap); + if (!__msan_has_dynamic_component()) { + __msan_unpoison(str, 4 * (res + 1)); + } + return res; +} + +INTERCEPTOR(int, sprintf, char *str, const char *format, ...) { // NOLINT + ENSURE_MSAN_INITED(); + va_list ap; + va_start(ap, format); + int res = vsprintf(str, format, ap); // NOLINT + va_end(ap); + return res; +} + +INTERCEPTOR(int, snprintf, char *str, uptr size, const char *format, ...) { + ENSURE_MSAN_INITED(); + va_list ap; + va_start(ap, format); + int res = vsnprintf(str, size, format, ap); + va_end(ap); + return res; +} + +INTERCEPTOR(int, swprintf, void *str, uptr size, void *format, ...) { + ENSURE_MSAN_INITED(); + va_list ap; + va_start(ap, format); + int res = vswprintf(str, size, format, ap); + va_end(ap); + return res; +} + +// SIZE_T strftime(char *s, SIZE_T max, const char *format,const struct tm *tm); +INTERCEPTOR(SIZE_T, strftime, char *s, SIZE_T max, const char *format, + void *tm) { + ENSURE_MSAN_INITED(); + SIZE_T res = REAL(strftime)(s, max, format, tm); + if (res) __msan_unpoison(s, res + 1); + return res; +} + +INTERCEPTOR(SIZE_T, wcstombs, void *dest, void *src, SIZE_T size) { + ENSURE_MSAN_INITED(); + SIZE_T res = REAL(wcstombs)(dest, src, size); + if (res != (SIZE_T)-1) __msan_unpoison(dest, res + 1); + return res; +} + +// SIZE_T mbstowcs(wchar_t *dest, const char *src, SIZE_T n); +INTERCEPTOR(SIZE_T, mbstowcs, wchar_t *dest, const char *src, SIZE_T n) { + ENSURE_MSAN_INITED(); + SIZE_T res = REAL(mbstowcs)(dest, src, n); + if (res != (SIZE_T)-1) __msan_unpoison(dest, (res + 1) * sizeof(wchar_t)); + return res; +} + +INTERCEPTOR(SIZE_T, wcslen, const wchar_t *s) { + ENSURE_MSAN_INITED(); + SIZE_T res = REAL(wcslen)(s); + CHECK_UNPOISONED(s, sizeof(wchar_t) * (res + 1)); + return res; +} + +// wchar_t *wcschr(const wchar_t *wcs, wchar_t wc); +INTERCEPTOR(wchar_t *, wcschr, void *s, wchar_t wc, void *ps) { + ENSURE_MSAN_INITED(); + wchar_t *res = REAL(wcschr)(s, wc, ps); + return res; +} + +// wchar_t *wcscpy(wchar_t *dest, const wchar_t *src); +INTERCEPTOR(wchar_t *, wcscpy, wchar_t *dest, const wchar_t *src) { + ENSURE_MSAN_INITED(); + wchar_t *res = REAL(wcscpy)(dest, src); + __msan_copy_poison(dest, src, sizeof(wchar_t) * (REAL(wcslen)(src) + 1)); + return res; +} + +// wchar_t *wmemcpy(wchar_t *dest, const wchar_t *src, SIZE_T n); +INTERCEPTOR(wchar_t *, wmemcpy, wchar_t *dest, const wchar_t *src, SIZE_T n) { + ENSURE_MSAN_INITED(); + wchar_t *res = REAL(wmemcpy)(dest, src, n); + __msan_copy_poison(dest, src, n * sizeof(wchar_t)); + return res; +} + +INTERCEPTOR(wchar_t *, wmemset, wchar_t *s, wchar_t c, SIZE_T n) { + CHECK(MEM_IS_APP(s)); + ENSURE_MSAN_INITED(); + wchar_t *res = (wchar_t *)fast_memset(s, c, n * sizeof(wchar_t)); + __msan_unpoison(s, n * sizeof(wchar_t)); + return res; +} + +INTERCEPTOR(wchar_t *, wmemmove, wchar_t *dest, const wchar_t *src, SIZE_T n) { + ENSURE_MSAN_INITED(); + wchar_t *res = REAL(wmemmove)(dest, src, n); + __msan_move_poison(dest, src, n * sizeof(wchar_t)); + return res; +} + +INTERCEPTOR(int, wcscmp, const wchar_t *s1, const wchar_t *s2) { + ENSURE_MSAN_INITED(); + int res = REAL(wcscmp)(s1, s2); + return res; +} + +INTERCEPTOR(double, wcstod, const wchar_t *nptr, wchar_t **endptr) { + ENSURE_MSAN_INITED(); + double res = REAL(wcstod)(nptr, endptr); + __msan_unpoison(endptr, sizeof(*endptr)); + return res; +} + +// #define UNSUPPORTED(name) \ +// INTERCEPTOR(void, name, void) { \ +// Printf("MSAN: Unsupported %s\n", __FUNCTION__); \ +// Die(); \ +// } + +// FIXME: intercept the following functions: +// Note, they only matter when running without a dynamic tool. +// UNSUPPORTED(wcscoll_l) +// UNSUPPORTED(wcsnrtombs) +// UNSUPPORTED(wcstol) +// UNSUPPORTED(wcstoll) +// UNSUPPORTED(wcstold) +// UNSUPPORTED(wcstoul) +// UNSUPPORTED(wcstoull) +// UNSUPPORTED(wcsxfrm_l) +// UNSUPPORTED(wcsdup) +// UNSUPPORTED(wcsftime) +// UNSUPPORTED(wcsstr) +// UNSUPPORTED(wcsrchr) +// UNSUPPORTED(wctob) + +INTERCEPTOR(int, gettimeofday, void *tv, void *tz) { + ENSURE_MSAN_INITED(); + int res = REAL(gettimeofday)(tv, tz); + if (tv) + __msan_unpoison(tv, 16); + if (tz) + __msan_unpoison(tz, 8); + return res; +} + +INTERCEPTOR(char *, fcvt, double x, int a, int *b, int *c) { + ENSURE_MSAN_INITED(); + char *res = REAL(fcvt)(x, a, b, c); + if (!__msan_has_dynamic_component()) { + __msan_unpoison(b, sizeof(*b)); + __msan_unpoison(c, sizeof(*c)); + } + return res; +} + +INTERCEPTOR(char *, getenv, char *name) { + ENSURE_MSAN_INITED(); + char *res = REAL(getenv)(name); + if (!__msan_has_dynamic_component()) { + if (res) + __msan_unpoison(res, REAL(strlen)(res) + 1); + } + return res; +} + +INTERCEPTOR(int, __fxstat, int magic, int fd, void *buf) { + ENSURE_MSAN_INITED(); + int res = REAL(__fxstat)(magic, fd, buf); + if (!res) + __msan_unpoison(buf, __msan::struct_stat_sz); + return res; +} + +INTERCEPTOR(int, __fxstat64, int magic, int fd, void *buf) { + ENSURE_MSAN_INITED(); + int res = REAL(__fxstat64)(magic, fd, buf); + if (!res) + __msan_unpoison(buf, __msan::struct_stat64_sz); + return res; +} + +INTERCEPTOR(int, __xstat, int magic, char *path, void *buf) { + ENSURE_MSAN_INITED(); + int res = REAL(__xstat)(magic, path, buf); + if (!res) + __msan_unpoison(buf, __msan::struct_stat_sz); + return res; +} + +INTERCEPTOR(int, __xstat64, int magic, char *path, void *buf) { + ENSURE_MSAN_INITED(); + int res = REAL(__xstat64)(magic, path, buf); + if (!res) + __msan_unpoison(buf, __msan::struct_stat64_sz); + return res; +} + +INTERCEPTOR(int, __lxstat, int magic, char *path, void *buf) { + ENSURE_MSAN_INITED(); + int res = REAL(__lxstat)(magic, path, buf); + if (!res) + __msan_unpoison(buf, __msan::struct_stat_sz); + return res; +} + +INTERCEPTOR(int, __lxstat64, int magic, char *path, void *buf) { + ENSURE_MSAN_INITED(); + int res = REAL(__lxstat64)(magic, path, buf); + if (!res) + __msan_unpoison(buf, __msan::struct_stat64_sz); + return res; +} + +INTERCEPTOR(int, pipe, int pipefd[2]) { + if (msan_init_is_running) + return REAL(pipe)(pipefd); + ENSURE_MSAN_INITED(); + int res = REAL(pipe)(pipefd); + if (!res) + __msan_unpoison(pipefd, sizeof(int[2])); + return res; +} + +INTERCEPTOR(int, wait, int *status) { + ENSURE_MSAN_INITED(); + int res = REAL(wait)(status); + if (status) + __msan_unpoison(status, sizeof(*status)); + return res; +} + +INTERCEPTOR(int, waitpid, int pid, int *status, int options) { + ENSURE_MSAN_INITED(); + int res = REAL(waitpid)(pid, status, options); + if (status) + __msan_unpoison(status, sizeof(*status)); + return res; +} + +INTERCEPTOR(char *, fgets, char *s, int size, void *stream) { + ENSURE_MSAN_INITED(); + char *res = REAL(fgets)(s, size, stream); + if (res) + __msan_unpoison(s, REAL(strlen)(s) + 1); + return res; +} + +INTERCEPTOR(char *, fgets_unlocked, char *s, int size, void *stream) { + ENSURE_MSAN_INITED(); + char *res = REAL(fgets_unlocked)(s, size, stream); + if (res) + __msan_unpoison(s, REAL(strlen)(s) + 1); + return res; +} + +INTERCEPTOR(char *, getcwd, char *buf, SIZE_T size) { + ENSURE_MSAN_INITED(); + char *res = REAL(getcwd)(buf, size); + if (res) + __msan_unpoison(buf, REAL(strlen)(buf) + 1); + return res; +} + +INTERCEPTOR(char *, realpath, char *path, char *abspath) { + ENSURE_MSAN_INITED(); + char *res = REAL(realpath)(path, abspath); + if (res) + __msan_unpoison(abspath, REAL(strlen)(abspath) + 1); + return res; +} + +INTERCEPTOR(int, getrlimit, int resource, void *rlim) { + if (msan_init_is_running) + return REAL(getrlimit)(resource, rlim); + ENSURE_MSAN_INITED(); + int res = REAL(getrlimit)(resource, rlim); + if (!res) + __msan_unpoison(rlim, __msan::struct_rlimit_sz); + return res; +} + +INTERCEPTOR(int, getrlimit64, int resource, void *rlim) { + if (msan_init_is_running) + return REAL(getrlimit64)(resource, rlim); + ENSURE_MSAN_INITED(); + int res = REAL(getrlimit64)(resource, rlim); + if (!res) + __msan_unpoison(rlim, __msan::struct_rlimit64_sz); + return res; +} + +INTERCEPTOR(int, statfs, const char *s, void *buf) { + ENSURE_MSAN_INITED(); + int res = REAL(statfs)(s, buf); + if (!res) + __msan_unpoison(buf, __msan::struct_statfs_sz); + return res; +} + +INTERCEPTOR(int, fstatfs, int fd, void *buf) { + ENSURE_MSAN_INITED(); + int res = REAL(fstatfs)(fd, buf); + if (!res) + __msan_unpoison(buf, __msan::struct_statfs_sz); + return res; +} + +INTERCEPTOR(int, statfs64, const char *s, void *buf) { + ENSURE_MSAN_INITED(); + int res = REAL(statfs64)(s, buf); + if (!res) + __msan_unpoison(buf, __msan::struct_statfs64_sz); + return res; +} + +INTERCEPTOR(int, fstatfs64, int fd, void *buf) { + ENSURE_MSAN_INITED(); + int res = REAL(fstatfs64)(fd, buf); + if (!res) + __msan_unpoison(buf, __msan::struct_statfs64_sz); + return res; +} + +INTERCEPTOR(int, uname, void *utsname) { + ENSURE_MSAN_INITED(); + int res = REAL(uname)(utsname); + if (!res) { + __msan_unpoison(utsname, __msan::struct_utsname_sz); + } + return res; +} + +INTERCEPTOR(int, epoll_wait, int epfd, void *events, int maxevents, + int timeout) { + ENSURE_MSAN_INITED(); + int res = REAL(epoll_wait)(epfd, events, maxevents, timeout); + if (res > 0) { + __msan_unpoison(events, __msan::struct_epoll_event_sz * res); + } + return res; +} + +INTERCEPTOR(int, epoll_pwait, int epfd, void *events, int maxevents, + int timeout, void *sigmask) { + ENSURE_MSAN_INITED(); + int res = REAL(epoll_pwait)(epfd, events, maxevents, timeout, sigmask); + if (res > 0) { + __msan_unpoison(events, __msan::struct_epoll_event_sz * res); + } + return res; +} + +INTERCEPTOR(SSIZE_T, recv, int fd, void *buf, SIZE_T len, int flags) { + ENSURE_MSAN_INITED(); + SSIZE_T res = REAL(recv)(fd, buf, len, flags); + if (res > 0) + __msan_unpoison(buf, res); + return res; +} + +INTERCEPTOR(SSIZE_T, recvfrom, int fd, void *buf, SIZE_T len, int flags, + void *srcaddr, void *addrlen) { + ENSURE_MSAN_INITED(); + SIZE_T srcaddr_sz; + if (srcaddr) + srcaddr_sz = __msan_get_socklen_t(addrlen); + SSIZE_T res = REAL(recvfrom)(fd, buf, len, flags, srcaddr, addrlen); + if (res > 0) { + __msan_unpoison(buf, res); + if (srcaddr) { + SIZE_T sz = __msan_get_socklen_t(addrlen); + __msan_unpoison(srcaddr, (sz < srcaddr_sz) ? sz : srcaddr_sz); + } + } + return res; +} + +INTERCEPTOR(SSIZE_T, recvmsg, int fd, struct msghdr *msg, int flags) { + ENSURE_MSAN_INITED(); + SSIZE_T res = REAL(recvmsg)(fd, msg, flags); + if (res > 0) { + for (SIZE_T i = 0; i < __msan_get_msghdr_iovlen(msg); ++i) + __msan_unpoison(__msan_get_msghdr_iov_iov_base(msg, i), + __msan_get_msghdr_iov_iov_len(msg, i)); + } + return res; +} + +INTERCEPTOR(void *, calloc, SIZE_T nmemb, SIZE_T size) { + GET_MALLOC_STACK_TRACE; + if (!msan_inited) { + // Hack: dlsym calls calloc before REAL(calloc) is retrieved from dlsym. + const SIZE_T kCallocPoolSize = 1024; + static uptr calloc_memory_for_dlsym[kCallocPoolSize]; + static SIZE_T allocated; + SIZE_T size_in_words = ((nmemb * size) + kWordSize - 1) / kWordSize; + void *mem = (void*)&calloc_memory_for_dlsym[allocated]; + allocated += size_in_words; + CHECK(allocated < kCallocPoolSize); + return mem; + } + return MsanReallocate(&stack, 0, nmemb * size, sizeof(u64), true); +} + +INTERCEPTOR(void *, realloc, void *ptr, SIZE_T size) { + GET_MALLOC_STACK_TRACE; + return MsanReallocate(&stack, ptr, size, sizeof(u64), false); +} + +INTERCEPTOR(void *, malloc, SIZE_T size) { + GET_MALLOC_STACK_TRACE; + return MsanReallocate(&stack, 0, size, sizeof(u64), false); +} + +INTERCEPTOR(void *, mmap, void *addr, SIZE_T length, int prot, int flags, + int fd, OFF_T offset) { + ENSURE_MSAN_INITED(); + void *res = REAL(mmap)(addr, length, prot, flags, fd, offset); + if (res != (void*)-1) + __msan_unpoison(res, RoundUpTo(length, GetPageSize())); + return res; +} + +INTERCEPTOR(void *, mmap64, void *addr, SIZE_T length, int prot, int flags, + int fd, OFF64_T offset) { + ENSURE_MSAN_INITED(); + void *res = REAL(mmap64)(addr, length, prot, flags, fd, offset); + if (res != (void*)-1) + __msan_unpoison(res, RoundUpTo(length, GetPageSize())); + return res; +} + +struct dlinfo { + char *dli_fname; + void *dli_fbase; + char *dli_sname; + void *dli_saddr; +}; + +INTERCEPTOR(int, dladdr, void *addr, dlinfo *info) { + ENSURE_MSAN_INITED(); + int res = REAL(dladdr)(addr, info); + if (res != 0) { + __msan_unpoison(info, sizeof(*info)); + if (info->dli_fname) + __msan_unpoison(info->dli_fname, REAL(strlen)(info->dli_fname) + 1); + if (info->dli_sname) + __msan_unpoison(info->dli_sname, REAL(strlen)(info->dli_sname) + 1); + } + return res; +} + +INTERCEPTOR(int, getrusage, int who, void *usage) { + ENSURE_MSAN_INITED(); + int res = REAL(getrusage)(who, usage); + if (res == 0) { + __msan_unpoison(usage, __msan::struct_rusage_sz); + } + return res; +} + +#define COMMON_INTERCEPTOR_WRITE_RANGE(ctx, ptr, size) \ + __msan_unpoison(ptr, size) +#define COMMON_INTERCEPTOR_READ_RANGE(ctx, ptr, size) do { } while (false) +#define COMMON_INTERCEPTOR_ENTER(ctx, func, ...) \ + do { \ + ctx = 0; \ + (void)ctx; \ + ENSURE_MSAN_INITED(); \ + } while (false) +#define COMMON_INTERCEPTOR_FD_ACQUIRE(ctx, fd) do { } while (false) +#define COMMON_INTERCEPTOR_FD_RELEASE(ctx, fd) do { } while (false) +#define COMMON_INTERCEPTOR_SET_THREAD_NAME(ctx, name) \ + do { } while (false) // FIXME +#include "sanitizer_common/sanitizer_common_interceptors.inc" + +// static +void *fast_memset(void *ptr, int c, SIZE_T n) { + // hack until we have a really fast internal_memset + if (sizeof(uptr) == 8 && + (n % 8) == 0 && + ((uptr)ptr % 8) == 0 && + (c == 0 || c == -1)) { + // Printf("memset %p %zd %x\n", ptr, n, c); + uptr to_store = c ? -1L : 0L; + uptr *p = (uptr*)ptr; + for (SIZE_T i = 0; i < n / 8; i++) + p[i] = to_store; + return ptr; + } + return internal_memset(ptr, c, n); +} + +// static +void *fast_memcpy(void *dst, const void *src, SIZE_T n) { + // Same hack as in fast_memset above. + if (sizeof(uptr) == 8 && + (n % 8) == 0 && + ((uptr)dst % 8) == 0 && + ((uptr)src % 8) == 0) { + uptr *d = (uptr*)dst; + uptr *s = (uptr*)src; + for (SIZE_T i = 0; i < n / 8; i++) + d[i] = s[i]; + return dst; + } + return internal_memcpy(dst, src, n); +} + +// These interface functions reside here so that they can use +// fast_memset, etc. +void __msan_unpoison(void *a, uptr size) { + if (!MEM_IS_APP(a)) return; + fast_memset((void*)MEM_TO_SHADOW((uptr)a), 0, size); +} + +void __msan_poison(void *a, uptr size) { + if (!MEM_IS_APP(a)) return; + fast_memset((void*)MEM_TO_SHADOW((uptr)a), + __msan::flags()->poison_heap_with_zeroes ? 0 : -1, size); +} + +void __msan_poison_stack(void *a, uptr size) { + if (!MEM_IS_APP(a)) return; + fast_memset((void*)MEM_TO_SHADOW((uptr)a), + __msan::flags()->poison_stack_with_zeroes ? 0 : -1, size); +} + +void __msan_clear_and_unpoison(void *a, uptr size) { + fast_memset(a, 0, size); + fast_memset((void*)MEM_TO_SHADOW((uptr)a), 0, size); +} + +void __msan_copy_origin(void *dst, const void *src, uptr size) { + if (!__msan_get_track_origins()) return; + if (!MEM_IS_APP(dst) || !MEM_IS_APP(src)) return; + uptr d = MEM_TO_ORIGIN(dst); + uptr s = MEM_TO_ORIGIN(src); + uptr beg = d & ~3UL; // align down. + uptr end = (d + size + 3) & ~3UL; // align up. + s = s & ~3UL; // align down. + fast_memcpy((void*)beg, (void*)s, end - beg); +} + +void __msan_copy_poison(void *dst, const void *src, uptr size) { + if (!MEM_IS_APP(dst)) return; + if (!MEM_IS_APP(src)) return; + fast_memcpy((void*)MEM_TO_SHADOW((uptr)dst), + (void*)MEM_TO_SHADOW((uptr)src), size); + __msan_copy_origin(dst, src, size); +} + +void __msan_move_poison(void *dst, const void *src, uptr size) { + if (!MEM_IS_APP(dst)) return; + if (!MEM_IS_APP(src)) return; + internal_memmove((void*)MEM_TO_SHADOW((uptr)dst), + (void*)MEM_TO_SHADOW((uptr)src), size); + __msan_copy_origin(dst, src, size); +} + +void *__msan_memcpy(void *dest, const void *src, SIZE_T n) { + ENSURE_MSAN_INITED(); + void *res = fast_memcpy(dest, src, n); + __msan_copy_poison(dest, src, n); + return res; +} + +void *__msan_memset(void *s, int c, SIZE_T n) { + ENSURE_MSAN_INITED(); + void *res = fast_memset(s, c, n); + __msan_unpoison(s, n); + return res; +} + +void *__msan_memmove(void *dest, const void *src, SIZE_T n) { + ENSURE_MSAN_INITED(); + void *res = REAL(memmove)(dest, src, n); + __msan_move_poison(dest, src, n); + return res; +} + +namespace __msan { +void InitializeInterceptors() { + static int inited = 0; + CHECK_EQ(inited, 0); + SANITIZER_COMMON_INTERCEPTORS_INIT; + + INTERCEPT_FUNCTION(mmap); + INTERCEPT_FUNCTION(mmap64); + INTERCEPT_FUNCTION(posix_memalign); + INTERCEPT_FUNCTION(malloc); + INTERCEPT_FUNCTION(calloc); + INTERCEPT_FUNCTION(realloc); + INTERCEPT_FUNCTION(free); + INTERCEPT_FUNCTION(fread); + INTERCEPT_FUNCTION(fread_unlocked); + INTERCEPT_FUNCTION(readlink); + INTERCEPT_FUNCTION(readdir); + INTERCEPT_FUNCTION(memcpy); + INTERCEPT_FUNCTION(memset); + INTERCEPT_FUNCTION(memmove); + INTERCEPT_FUNCTION(wmemset); + INTERCEPT_FUNCTION(wmemcpy); + INTERCEPT_FUNCTION(wmemmove); + INTERCEPT_FUNCTION(strcpy); // NOLINT + INTERCEPT_FUNCTION(strdup); + INTERCEPT_FUNCTION(strncpy); // NOLINT + INTERCEPT_FUNCTION(strlen); + INTERCEPT_FUNCTION(strnlen); + INTERCEPT_FUNCTION(gcvt); + INTERCEPT_FUNCTION(strcat); // NOLINT + INTERCEPT_FUNCTION(strncat); // NOLINT + INTERCEPT_FUNCTION(strtol); + INTERCEPT_FUNCTION(strtoll); + INTERCEPT_FUNCTION(strtoul); + INTERCEPT_FUNCTION(strtoull); + INTERCEPT_FUNCTION(strtod); + INTERCEPT_FUNCTION(strtof); + INTERCEPT_FUNCTION(strtold); + INTERCEPT_FUNCTION(vsprintf); + INTERCEPT_FUNCTION(vsnprintf); + INTERCEPT_FUNCTION(vswprintf); + INTERCEPT_FUNCTION(sprintf); // NOLINT + INTERCEPT_FUNCTION(snprintf); + INTERCEPT_FUNCTION(swprintf); + INTERCEPT_FUNCTION(strftime); + INTERCEPT_FUNCTION(wcstombs); + INTERCEPT_FUNCTION(mbstowcs); + INTERCEPT_FUNCTION(wcslen); + INTERCEPT_FUNCTION(wcschr); + INTERCEPT_FUNCTION(wcscpy); + INTERCEPT_FUNCTION(wcscmp); + INTERCEPT_FUNCTION(wcstod); + INTERCEPT_FUNCTION(getenv); + INTERCEPT_FUNCTION(gettimeofday); + INTERCEPT_FUNCTION(fcvt); + INTERCEPT_FUNCTION(__fxstat); + INTERCEPT_FUNCTION(__xstat); + INTERCEPT_FUNCTION(__lxstat); + INTERCEPT_FUNCTION(__fxstat64); + INTERCEPT_FUNCTION(__xstat64); + INTERCEPT_FUNCTION(__lxstat64); + INTERCEPT_FUNCTION(pipe); + INTERCEPT_FUNCTION(wait); + INTERCEPT_FUNCTION(waitpid); + INTERCEPT_FUNCTION(fgets); + INTERCEPT_FUNCTION(fgets_unlocked); + INTERCEPT_FUNCTION(getcwd); + INTERCEPT_FUNCTION(realpath); + INTERCEPT_FUNCTION(getrlimit); + INTERCEPT_FUNCTION(getrlimit64); + INTERCEPT_FUNCTION(statfs); + INTERCEPT_FUNCTION(fstatfs); + INTERCEPT_FUNCTION(statfs64); + INTERCEPT_FUNCTION(fstatfs64); + INTERCEPT_FUNCTION(uname); + INTERCEPT_FUNCTION(epoll_wait); + INTERCEPT_FUNCTION(epoll_pwait); + INTERCEPT_FUNCTION(recv); + INTERCEPT_FUNCTION(recvfrom); + INTERCEPT_FUNCTION(recvmsg); + INTERCEPT_FUNCTION(dladdr); + INTERCEPT_FUNCTION(getrusage); + inited = 1; +} +} // namespace __msan diff --git a/lib/msan/msan_linux.cc b/lib/msan/msan_linux.cc new file mode 100644 index 000000000000..2203980c638d --- /dev/null +++ b/lib/msan/msan_linux.cc @@ -0,0 +1,108 @@ +//===-- msan_linux.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 MemorySanitizer. +// +// Linux-specific code. +//===----------------------------------------------------------------------===// + +#ifdef __linux__ + +#include "msan.h" + +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> +#include <unistd.h> +#include <unwind.h> +#include <execinfo.h> +#include <sys/time.h> +#include <sys/resource.h> + +#include "sanitizer_common/sanitizer_common.h" +#include "sanitizer_common/sanitizer_procmaps.h" + +namespace __msan { + +static const uptr kMemBeg = 0x600000000000; +static const uptr kMemEnd = 0x7fffffffffff; +static const uptr kShadowBeg = MEM_TO_SHADOW(kMemBeg); +static const uptr kShadowEnd = MEM_TO_SHADOW(kMemEnd); +static const uptr kBad1Beg = 0x100000000; // 4G +static const uptr kBad1End = kShadowBeg - 1; +static const uptr kBad2Beg = kShadowEnd + 1; +static const uptr kBad2End = kMemBeg - 1; +static const uptr kOriginsBeg = kBad2Beg; +static const uptr kOriginsEnd = kBad2End; + +bool InitShadow(bool prot1, bool prot2, bool map_shadow, bool init_origins) { + if (flags()->verbosity) { + Printf("__msan_init %p\n", &__msan_init); + Printf("Memory : %p %p\n", kMemBeg, kMemEnd); + Printf("Bad2 : %p %p\n", kBad2Beg, kBad2End); + Printf("Origins : %p %p\n", kOriginsBeg, kOriginsEnd); + Printf("Shadow : %p %p\n", kShadowBeg, kShadowEnd); + Printf("Bad1 : %p %p\n", kBad1Beg, kBad1End); + } + + if (!MemoryRangeIsAvailable(kShadowBeg, + init_origins ? kOriginsEnd : kShadowEnd)) { + Printf("FATAL: Shadow memory range is not available.\n"); + return false; + } + + if (prot1 && !Mprotect(kBad1Beg, kBad1End - kBad1Beg)) + return false; + if (prot2 && !Mprotect(kBad2Beg, kBad2End - kBad2Beg)) + return false; + if (map_shadow) { + void *shadow = MmapFixedNoReserve(kShadowBeg, kShadowEnd - kShadowBeg); + if (shadow != (void*)kShadowBeg) return false; + } + if (init_origins) { + void *origins = MmapFixedNoReserve(kOriginsBeg, kOriginsEnd - kOriginsBeg); + if (origins != (void*)kOriginsBeg) return false; + } + return true; +} + +static void MsanTrap(int, siginfo_t *siginfo, void *context) { + ucontext_t *ucontext = (ucontext_t*)context; + uptr pc = ucontext->uc_mcontext.gregs[REG_RIP]; + uptr bp = ucontext->uc_mcontext.gregs[REG_RBP]; + PrintWarning(pc + 1 /*1 will be subtracted in StackTrace::Print */, bp); + ucontext->uc_mcontext.gregs[REG_RIP] += 2; +} + +void InstallTrapHandler() { + struct sigaction sigact; + internal_memset(&sigact, 0, sizeof(sigact)); + sigact.sa_sigaction = MsanTrap; + sigact.sa_flags = SA_SIGINFO; + CHECK_EQ(0, sigaction(SIGILL, &sigact, 0)); +} + +void MsanDie() { + _exit(flags()->exit_code); +} + +static void MsanAtExit(void) { + if (msan_report_count > 0) { + ReportAtExitStatistics(); + if (flags()->exit_code) + _exit(flags()->exit_code); + } +} + +void InstallAtExitHandler() { + atexit(MsanAtExit); +} +} + +#endif // __linux__ diff --git a/lib/msan/msan_new_delete.cc b/lib/msan/msan_new_delete.cc new file mode 100644 index 000000000000..c4efe2ef70ce --- /dev/null +++ b/lib/msan/msan_new_delete.cc @@ -0,0 +1,51 @@ +//===-- msan_new_delete.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 MemorySanitizer. +// +// Interceptors for operators new and delete. +//===----------------------------------------------------------------------===// + +#include "msan.h" + +#include <stddef.h> + +namespace __msan { +// This function is a no-op. We need it to make sure that object file +// with our replacements will actually be loaded from static MSan +// run-time library at link-time. +void ReplaceOperatorsNewAndDelete() { } +} + +using namespace __msan; // NOLINT + +// Fake std::nothrow_t to avoid including <new>. +namespace std { + struct nothrow_t {}; +} // namespace std + + +#define OPERATOR_NEW_BODY \ + GET_MALLOC_STACK_TRACE; \ + return MsanReallocate(&stack, 0, size, sizeof(u64), false) + +void *operator new(size_t size) { OPERATOR_NEW_BODY; } +void *operator new[](size_t size) { OPERATOR_NEW_BODY; } +void *operator new(size_t size, std::nothrow_t const&) { OPERATOR_NEW_BODY; } +void *operator new[](size_t size, std::nothrow_t const&) { OPERATOR_NEW_BODY; } + +#define OPERATOR_DELETE_BODY \ + if (ptr) MsanDeallocate(ptr) + +void operator delete(void *ptr) { OPERATOR_DELETE_BODY; } +void operator delete[](void *ptr) { OPERATOR_DELETE_BODY; } +void operator delete(void *ptr, std::nothrow_t const&) { OPERATOR_DELETE_BODY; } +void operator delete[](void *ptr, std::nothrow_t const&) { + OPERATOR_DELETE_BODY; +} diff --git a/lib/msan/msan_platform_limits_posix.cc b/lib/msan/msan_platform_limits_posix.cc new file mode 100644 index 000000000000..19d6c5d0ab3f --- /dev/null +++ b/lib/msan/msan_platform_limits_posix.cc @@ -0,0 +1,59 @@ +//===-- msan_platform_limits.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 MemorySanitizer. +// +// Sizes and layouts of platform-specific POSIX data structures. +//===----------------------------------------------------------------------===// + +#ifdef __linux__ + +#include "msan.h" +#include "msan_platform_limits_posix.h" + +#include <sys/utsname.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <sys/vfs.h> +#include <sys/epoll.h> +#include <sys/socket.h> +#include <dirent.h> + +namespace __msan { + unsigned struct_utsname_sz = sizeof(struct utsname); + unsigned struct_stat_sz = sizeof(struct stat); + unsigned struct_stat64_sz = sizeof(struct stat64); + unsigned struct_rlimit_sz = sizeof(struct rlimit); + unsigned struct_rlimit64_sz = sizeof(struct rlimit64); + unsigned struct_dirent_sz = sizeof(struct dirent); + unsigned struct_statfs_sz = sizeof(struct statfs); + unsigned struct_statfs64_sz = sizeof(struct statfs64); + unsigned struct_epoll_event_sz = sizeof(struct epoll_event); + unsigned struct_rusage_sz = sizeof(struct rusage); + + void* __msan_get_msghdr_iov_iov_base(void* msg, int idx) { + return ((struct msghdr *)msg)->msg_iov[idx].iov_base; + } + + uptr __msan_get_msghdr_iov_iov_len(void* msg, int idx) { + return ((struct msghdr *)msg)->msg_iov[idx].iov_len; + } + + uptr __msan_get_msghdr_iovlen(void* msg) { + return ((struct msghdr *)msg)->msg_iovlen; + } + + uptr __msan_get_socklen_t(void* socklen_ptr) { + return *(socklen_t*)socklen_ptr; + } +} + +#endif // __linux__ diff --git a/lib/msan/msan_platform_limits_posix.h b/lib/msan/msan_platform_limits_posix.h new file mode 100644 index 000000000000..3cd90ce93f6c --- /dev/null +++ b/lib/msan/msan_platform_limits_posix.h @@ -0,0 +1,36 @@ +//===-- msan_platform_limits.h ----------------------------------*- C++ -*-===// +// +// 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 MemorySanitizer. +// +// Sizes and layouts of platform-specific data structures. +//===----------------------------------------------------------------------===// + +#ifndef MSAN_PLATFORM_LIMITS_H +#define MSAN_PLATFORM_LIMITS_H + +namespace __msan { + extern unsigned struct_utsname_sz; + extern unsigned struct_stat_sz; + extern unsigned struct_stat64_sz; + extern unsigned struct_rlimit_sz; + extern unsigned struct_rlimit64_sz; + extern unsigned struct_dirent_sz; + extern unsigned struct_statfs_sz; + extern unsigned struct_statfs64_sz; + extern unsigned struct_epoll_event_sz; + extern unsigned struct_rusage_sz; + + void* __msan_get_msghdr_iov_iov_base(void* msg, int idx); + uptr __msan_get_msghdr_iov_iov_len(void* msg, int idx); + uptr __msan_get_msghdr_iovlen(void* msg); + uptr __msan_get_socklen_t(void* socklen_ptr); +} // namespace __msan + +#endif diff --git a/lib/msan/msan_report.cc b/lib/msan/msan_report.cc new file mode 100644 index 000000000000..872108999733 --- /dev/null +++ b/lib/msan/msan_report.cc @@ -0,0 +1,100 @@ +//===-- msan_report.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 MemorySanitizer. +// +// Error reporting. +//===----------------------------------------------------------------------===// + +#include "msan.h" +#include "sanitizer_common/sanitizer_common.h" +#include "sanitizer_common/sanitizer_mutex.h" +#include "sanitizer_common/sanitizer_report_decorator.h" +#include "sanitizer_common/sanitizer_stackdepot.h" + +using namespace __sanitizer; + +static StaticSpinMutex report_mu; + +namespace __msan { + +static bool PrintsToTtyCached() { + static int cached = 0; + static bool prints_to_tty; + if (!cached) { // Ok wrt threads since we are printing only from one thread. + prints_to_tty = PrintsToTty(); + cached = 1; + } + return prints_to_tty; +} + +class Decorator: private __sanitizer::AnsiColorDecorator { + public: + Decorator() : __sanitizer::AnsiColorDecorator(PrintsToTtyCached()) { } + const char *Warning() { return Red(); } + const char *Origin() { return Magenta(); } + const char *Name() { return Green(); } + const char *End() { return Default(); } +}; + +static void DescribeOrigin(u32 origin) { + Decorator d; + if (flags()->verbosity) + Printf(" raw origin id: %d\n", origin); + if (const char *so = __msan_get_origin_descr_if_stack(origin)) { + char* s = internal_strdup(so); + char* sep = internal_strchr(s, '@'); + CHECK(sep); + *sep = '\0'; + Printf("%s", d.Origin()); + Printf(" %sUninitialised value was created by an allocation of '%s%s%s'" + " in the stack frame of function '%s%s%s'%s\n", + d.Origin(), d.Name(), s, d.Origin(), d.Name(), sep + 1, + d.Origin(), d.End()); + InternalFree(s); + } else { + uptr size = 0; + const uptr *trace = StackDepotGet(origin, &size); + Printf(" %sUninitialised value was created by a heap allocation%s\n", + d.Origin(), d.End()); + StackTrace::PrintStack(trace, size, true, "", 0); + } +} + +void ReportUMR(StackTrace *stack, u32 origin) { + if (!__msan::flags()->report_umrs) return; + + GenericScopedLock<StaticSpinMutex> lock(&report_mu); + + Decorator d; + Printf("%s", d.Warning()); + Report(" WARNING: Use of uninitialized value\n"); + Printf("%s", d.End()); + StackTrace::PrintStack(stack->trace, stack->size, true, "", 0); + if (origin) { + DescribeOrigin(origin); + } +} + +void ReportExpectedUMRNotFound(StackTrace *stack) { + GenericScopedLock<StaticSpinMutex> lock(&report_mu); + + Printf(" WARNING: Expected use of uninitialized value not found\n"); + StackTrace::PrintStack(stack->trace, stack->size, true, "", 0); +} + +void ReportAtExitStatistics() { + Decorator d; + Printf("%s", d.Warning()); + Printf("MemorySanitizer: %d warnings reported.\n", msan_report_count); + Printf("%s", d.End()); +} + + +} // namespace msan diff --git a/lib/msan/tests/CMakeLists.txt b/lib/msan/tests/CMakeLists.txt new file mode 100644 index 000000000000..d2a28b2cba5c --- /dev/null +++ b/lib/msan/tests/CMakeLists.txt @@ -0,0 +1,166 @@ +include(CheckCXXCompilerFlag) +include(CompilerRTCompile) +include(CompilerRTLink) + +include_directories(..) +include_directories(../..) + +# Instrumented libcxx sources and build flags. +set(MSAN_LIBCXX_PATH ${LLVM_MAIN_SRC_DIR}/projects/libcxx) +file(GLOB MSAN_LIBCXX_SOURCES ${MSAN_LIBCXX_PATH}/src/*.cpp) +set(MSAN_LIBCXX_CFLAGS + -I${MSAN_LIBCXX_PATH}/include + -fsanitize=memory + -fsanitize-memory-track-origins + -fPIC + -Wno-\#warnings + -g + -O2 + -std=c++0x + -fstrict-aliasing + -fno-exceptions + -nostdinc++ + -fno-omit-frame-pointer + -mno-omit-leaf-frame-pointer) +set(MSAN_LIBCXX_LINK_FLAGS + -nodefaultlibs + -lpthread + -lrt + -lc + -lstdc++ + -fsanitize=memory) + +# Unittest sources and build flags. +set(MSAN_UNITTEST_SOURCE msan_test.cc) +set(MSAN_UNITTEST_HEADERS msandr_test_so.h) +set(MSANDR_UNITTEST_SOURCE msandr_test_so.cc) +set(MSAN_UNITTEST_COMMON_CFLAGS + -I${MSAN_LIBCXX_PATH}/include + ${COMPILER_RT_GTEST_INCLUDE_CFLAGS} + -I${COMPILER_RT_SOURCE_DIR}/include + -I${COMPILER_RT_SOURCE_DIR}/lib + -I${COMPILER_RT_SOURCE_DIR}/lib/msan + -std=c++0x + -stdlib=libc++ + -fPIE + -g + -O2 + -fno-exceptions + -fno-omit-frame-pointer + -mno-omit-leaf-frame-pointer +) +set(MSAN_UNITTEST_INSTRUMENTED_CFLAGS + ${MSAN_UNITTEST_COMMON_CFLAGS} + -fsanitize=memory + -fsanitize-memory-track-origins + -mllvm -msan-keep-going=1 +) +set(MSAN_UNITTEST_LINK_FLAGS + -fsanitize=memory + -pie + -ldl + # FIXME: we build libcxx without cxxabi and need libstdc++ to provide it. + -lstdc++ +) + +# Compile source for the given architecture, using compiler +# options in ${ARGN}, and add it to the object list. +macro(msan_compile obj_list source arch) + get_filename_component(basename ${source} NAME) + set(output_obj "${basename}.${arch}.o") + get_target_flags_for_arch(${arch} TARGET_CFLAGS) + clang_compile(${output_obj} ${source} + CFLAGS ${ARGN} ${TARGET_CFLAGS} + DEPS gtest ${MSAN_RUNTIME_LIBRARIES} ${MSAN_UNITTEST_HEADERS}) + list(APPEND ${obj_list} ${output_obj}) +endmacro() + +macro(msan_link_shared so_list so_name arch) + parse_arguments(SOURCE "OBJECTS;LINKFLAGS;DEPS" "" ${ARGN}) + get_unittest_directory(OUTPUT_DIR) + file(MAKE_DIRECTORY ${OUTPUT_DIR}) + set(output_so "${OUTPUT_DIR}/${so_name}.${arch}.so") + get_target_flags_for_arch(${arch} TARGET_LINKFLAGS) + clang_link_shared(${output_so} + OBJECTS ${SOURCE_OBJECTS} + LINKFLAGS ${TARGET_LINKFLAGS} ${SOURCE_LINKFLAGS} + DEPS ${SOURCE_DEPS}) + list(APPEND ${so_list} ${output_so}) +endmacro() + +# Link MSan unit test for a given architecture from a set +# of objects in ${ARGN}. +macro(add_msan_test test_suite test_name arch) + get_target_flags_for_arch(${arch} TARGET_LINK_FLAGS) + get_unittest_directory(OUTPUT_DIR) + add_compiler_rt_test(${test_suite} ${test_name} + OBJECTS ${ARGN} + DEPS ${MSAN_RUNTIME_LIBRARIES} ${ARGN} + LINK_FLAGS ${MSAN_UNITTEST_LINK_FLAGS} + ${TARGET_LINK_FLAGS} + "-Wl,-rpath=${OUTPUT_DIR}") +endmacro() + +# Main MemorySanitizer unit tests. +add_custom_target(MsanUnitTests) +set_target_properties(MsanUnitTests PROPERTIES FOLDER "MSan unit tests") + +# Adds MSan unit tests and benchmarks for architecture. +macro(add_msan_tests_for_arch arch) + # Build gtest instrumented with MSan. + set(MSAN_INST_GTEST) + msan_compile(MSAN_INST_GTEST ${COMPILER_RT_GTEST_SOURCE} ${arch} + ${MSAN_UNITTEST_INSTRUMENTED_CFLAGS}) + + # Build libcxx instrumented with MSan. + set(MSAN_INST_LIBCXX_OBJECTS) + foreach(SOURCE ${MSAN_LIBCXX_SOURCES}) + msan_compile(MSAN_INST_LIBCXX_OBJECTS ${SOURCE} ${arch} + ${MSAN_LIBCXX_CFLAGS}) + endforeach(SOURCE) + + set(MSAN_INST_LIBCXX) + msan_link_shared(MSAN_INST_LIBCXX "libcxx" ${arch} + OBJECTS ${MSAN_INST_LIBCXX_OBJECTS} + LINKFLAGS ${MSAN_LIBCXX_LINK_FLAGS} + DEPS ${MSAN_INST_LIBCXX_OBJECTS} ${MSAN_RUNTIME_LIBRARIES}) + + # Instrumented tests. + set(MSAN_INST_TEST_OBJECTS) + msan_compile(MSAN_INST_TEST_OBJECTS ${MSAN_UNITTEST_SOURCE} ${arch} + ${MSAN_UNITTEST_INSTRUMENTED_CFLAGS}) + + # Uninstrumented shared object for MSanDR tests. + set(MSANDR_TEST_OBJECTS) + msan_compile(MSANDR_TEST_OBJECTS ${MSANDR_UNITTEST_SOURCE} ${arch} + ${MSAN_UNITTEST_COMMON_CFLAGS}) + + # Uninstrumented shared library tests. + set(MSANDR_TEST_SO) + msan_link_shared(MSANDR_TEST_SO "libmsandr_test" ${arch} + OBJECTS ${MSANDR_TEST_OBJECTS} + DEPS ${MSANDR_TEST_OBJECTS} ${MSAN_RUNTIME_LIBRARIES}) + + # Link everything together. + add_msan_test(MsanUnitTests "Msan-${arch}-Test" ${arch} + ${MSAN_INST_TEST_OBJECTS} ${MSAN_INST_GTEST} + ${MSAN_INST_LIBCXX} ${MSANDR_TEST_SO}) +endmacro() + +if(COMPILER_RT_CAN_EXECUTE_TESTS AND EXISTS ${MSAN_LIBCXX_PATH}/) + if(CAN_TARGET_x86_64) + add_msan_tests_for_arch(x86_64) + endif() + + # Run unittests as a part of lit testsuite. + configure_lit_site_cfg( + ${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.in + ${CMAKE_CURRENT_BINARY_DIR}/lit.site.cfg + ) + + add_lit_testsuite(check-msan "Running MemorySanitizer unittests" + ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS MsanUnitTests + ) + set_target_properties(check-msan PROPERTIES FOLDER "MemorySanitizer unittests") +endif() diff --git a/lib/msan/tests/lit.cfg b/lib/msan/tests/lit.cfg new file mode 100644 index 000000000000..38aa1380443f --- /dev/null +++ b/lib/msan/tests/lit.cfg @@ -0,0 +1,29 @@ +# -*- Python -*- + +import os + +def get_required_attr(config, attr_name): + attr_value = getattr(config, attr_name, None) + if not attr_value: + lit.fatal("No attribute %r in test configuration! You may need to run " + "tests from your build directory or add this attribute " + "to lit.site.cfg " % attr_name) + return attr_value + +# Setup attributes common for all compiler-rt projects. +llvm_src_root = get_required_attr(config, 'llvm_src_root') +compiler_rt_lit_unit_cfg = os.path.join(llvm_src_root, "projects", + "compiler-rt", "lib", + "lit.common.unit.cfg") +lit.load_config(config, compiler_rt_lit_unit_cfg) + +# Setup config name. +config.name = 'MemorySanitizer' + +# Setup test source and exec root. For unit tests, we define +# it as build directory with sanitizer_common unit tests. +llvm_obj_root = get_required_attr(config, "llvm_obj_root") +config.test_exec_root = os.path.join(llvm_obj_root, "projects", + "compiler-rt", "lib", + "msan", "tests") +config.test_source_root = config.test_exec_root diff --git a/lib/msan/tests/lit.site.cfg.in b/lib/msan/tests/lit.site.cfg.in new file mode 100644 index 000000000000..bb9a28d6a6cb --- /dev/null +++ b/lib/msan/tests/lit.site.cfg.in @@ -0,0 +1,9 @@ +## Autogenerated by LLVM/Clang configuration. +# Do not edit! + +config.build_type = "@CMAKE_BUILD_TYPE@" +config.llvm_obj_root = "@LLVM_BINARY_DIR@" +config.llvm_src_root = "@LLVM_SOURCE_DIR@" + +# Let the main config do the real work. +lit.load_config(config, "@CMAKE_CURRENT_SOURCE_DIR@/lit.cfg") diff --git a/lib/msan/tests/msan_test.cc b/lib/msan/tests/msan_test.cc new file mode 100644 index 000000000000..1ee7a27d56b1 --- /dev/null +++ b/lib/msan/tests/msan_test.cc @@ -0,0 +1,1675 @@ +//===-- msan_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 MemorySanitizer. +// +// MemorySanitizer unit tests. +//===----------------------------------------------------------------------===// + +#include "sanitizer/msan_interface.h" +#include "msandr_test_so.h" +#include "gtest/gtest.h" + +#include <stdlib.h> +#include <stdarg.h> +#include <stdio.h> +#include <assert.h> +#include <wchar.h> + +#include <dlfcn.h> +#include <unistd.h> +#include <limits.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <sys/resource.h> +#include <sys/ioctl.h> +#include <sys/utsname.h> +#include <sys/mman.h> +#include <sys/vfs.h> + +#if defined(__i386__) || defined(__x86_64__) +# include <emmintrin.h> +# define MSAN_HAS_M128 1 +#else +# define MSAN_HAS_M128 0 +#endif + +typedef unsigned char U1; +typedef unsigned short U2; // NOLINT +typedef unsigned int U4; +typedef unsigned long long U8; // NOLINT +typedef signed char S1; +typedef signed short S2; // NOLINT +typedef signed int S4; +typedef signed long long S8; // NOLINT +#define NOINLINE __attribute__((noinline)) +#define INLINE __attribute__((always_inline)) + + +#define EXPECT_POISONED(action) \ + do { \ + __msan_set_expect_umr(1); \ + action; \ + __msan_set_expect_umr(0); \ + } while (0) + +#define EXPECT_POISONED_O(action, origin) \ + do { \ + __msan_set_expect_umr(1); \ + action; \ + __msan_set_expect_umr(0); \ + if (TrackingOrigins()) \ + EXPECT_EQ(origin, __msan_get_origin_tls()); \ + } while (0) + +#define EXPECT_POISONED_S(action, stack_origin) \ + do { \ + __msan_set_expect_umr(1); \ + action; \ + __msan_set_expect_umr(0); \ + u32 id = __msan_get_origin_tls(); \ + const char *str = __msan_get_origin_descr_if_stack(id); \ + if (!str || strcmp(str, stack_origin)) { \ + fprintf(stderr, "EXPECT_POISONED_S: id=%u %s, %s", \ + id, stack_origin, str); \ + EXPECT_EQ(1, 0); \ + } \ + } while (0) + + +static U8 poisoned_array[100]; +template<class T> +T *GetPoisoned(int i = 0, T val = 0) { + T *res = (T*)&poisoned_array[i]; + *res = val; + __msan_poison(&poisoned_array[i], sizeof(T)); + return res; +} + +template<class T> +T *GetPoisonedO(int i, u32 origin, T val = 0) { + T *res = (T*)&poisoned_array[i]; + *res = val; + __msan_poison(&poisoned_array[i], sizeof(T)); + __msan_set_origin(&poisoned_array[i], sizeof(T), origin); + return res; +} + +// This function returns its parameter but in such a way that compiler +// can not prove it. +template<class T> +NOINLINE +static T Ident(T t) { + volatile T ret = t; + return ret; +} + +static bool TrackingOrigins() { + S8 x; + __msan_set_origin(&x, sizeof(x), 0x1234); + u32 origin = __msan_get_origin(&x); + __msan_set_origin(&x, sizeof(x), 0); + return origin == 0x1234; +} + +template<class T> NOINLINE T ReturnPoisoned() { return *GetPoisoned<T>(); } + +static volatile S1 v_s1; +static volatile S2 v_s2; +static volatile S4 v_s4; +static volatile S8 v_s8; +static volatile U1 v_u1; +static volatile U2 v_u2; +static volatile U4 v_u4; +static volatile U8 v_u8; +static void* volatile v_p; +static volatile double v_d; +static volatile int g_one = 1; +static volatile int g_zero = 0; +static volatile int g_0 = 0; +static volatile int g_1 = 1; + +#if MSAN_HAS_M128 +static volatile __m128i v_m128; +#endif + +S4 a_s4[100]; +S8 a_s8[100]; + +TEST(MemorySanitizer, NegativeTest1) { + S4 *x = GetPoisoned<S4>(); + if (g_one) + *x = 0; + v_s4 = *x; +} + +TEST(MemorySanitizer, PositiveTest1) { + // Load to store. + EXPECT_POISONED(v_s1 = *GetPoisoned<S1>()); + EXPECT_POISONED(v_s2 = *GetPoisoned<S2>()); + EXPECT_POISONED(v_s4 = *GetPoisoned<S4>()); + EXPECT_POISONED(v_s8 = *GetPoisoned<S8>()); + + // S->S conversions. + EXPECT_POISONED(v_s2 = *GetPoisoned<S1>()); + EXPECT_POISONED(v_s4 = *GetPoisoned<S1>()); + EXPECT_POISONED(v_s8 = *GetPoisoned<S1>()); + + EXPECT_POISONED(v_s1 = *GetPoisoned<S2>()); + EXPECT_POISONED(v_s4 = *GetPoisoned<S2>()); + EXPECT_POISONED(v_s8 = *GetPoisoned<S2>()); + + EXPECT_POISONED(v_s1 = *GetPoisoned<S4>()); + EXPECT_POISONED(v_s2 = *GetPoisoned<S4>()); + EXPECT_POISONED(v_s8 = *GetPoisoned<S4>()); + + EXPECT_POISONED(v_s1 = *GetPoisoned<S8>()); + EXPECT_POISONED(v_s2 = *GetPoisoned<S8>()); + EXPECT_POISONED(v_s4 = *GetPoisoned<S8>()); + + // ZExt + EXPECT_POISONED(v_s2 = *GetPoisoned<U1>()); + EXPECT_POISONED(v_s4 = *GetPoisoned<U1>()); + EXPECT_POISONED(v_s8 = *GetPoisoned<U1>()); + EXPECT_POISONED(v_s4 = *GetPoisoned<U2>()); + EXPECT_POISONED(v_s8 = *GetPoisoned<U2>()); + EXPECT_POISONED(v_s8 = *GetPoisoned<U4>()); + + // Unary ops. + EXPECT_POISONED(v_s4 = - *GetPoisoned<S4>()); + + EXPECT_POISONED(a_s4[g_zero] = 100 / *GetPoisoned<S4>(0, 1)); + + + a_s4[g_zero] = 1 - *GetPoisoned<S4>(); + a_s4[g_zero] = 1 + *GetPoisoned<S4>(); +} + +TEST(MemorySanitizer, Phi1) { + S4 c; + if (g_one) { + c = *GetPoisoned<S4>(); + } else { + __msan_break_optimization(0); + c = 0; + } + EXPECT_POISONED(v_s4 = c); +} + +TEST(MemorySanitizer, Phi2) { + S4 i = *GetPoisoned<S4>(); + S4 n = g_one; + EXPECT_POISONED(for (; i < g_one; i++);); + EXPECT_POISONED(v_s4 = i); +} + +NOINLINE void Arg1ExpectUMR(S4 a1) { EXPECT_POISONED(v_s4 = a1); } +NOINLINE void Arg2ExpectUMR(S4 a1, S4 a2) { EXPECT_POISONED(v_s4 = a2); } +NOINLINE void Arg3ExpectUMR(S1 a1, S4 a2, S8 a3) { EXPECT_POISONED(v_s8 = a3); } + +TEST(MemorySanitizer, ArgTest) { + Arg1ExpectUMR(*GetPoisoned<S4>()); + Arg2ExpectUMR(0, *GetPoisoned<S4>()); + Arg3ExpectUMR(0, 1, *GetPoisoned<S8>()); +} + + +TEST(MemorySanitizer, CallAndRet) { + if (!__msan_has_dynamic_component()) return; + ReturnPoisoned<S1>(); + ReturnPoisoned<S2>(); + ReturnPoisoned<S4>(); + ReturnPoisoned<S8>(); + + EXPECT_POISONED(v_s1 = ReturnPoisoned<S1>()); + EXPECT_POISONED(v_s2 = ReturnPoisoned<S2>()); + EXPECT_POISONED(v_s4 = ReturnPoisoned<S4>()); + EXPECT_POISONED(v_s8 = ReturnPoisoned<S8>()); +} + +// malloc() in the following test may be optimized to produce a compile-time +// undef value. Check that we trap on the volatile assignment anyway. +TEST(MemorySanitizer, DISABLED_MallocNoIdent) { + S4 *x = (int*)malloc(sizeof(S4)); + EXPECT_POISONED(v_s4 = *x); + free(x); +} + +TEST(MemorySanitizer, Malloc) { + S4 *x = (int*)Ident(malloc(sizeof(S4))); + EXPECT_POISONED(v_s4 = *x); + free(x); +} + +TEST(MemorySanitizer, Realloc) { + S4 *x = (int*)Ident(realloc(0, sizeof(S4))); + EXPECT_POISONED(v_s4 = x[0]); + x[0] = 1; + x = (int*)Ident(realloc(x, 2 * sizeof(S4))); + v_s4 = x[0]; // Ok, was inited before. + EXPECT_POISONED(v_s4 = x[1]); + x = (int*)Ident(realloc(x, 3 * sizeof(S4))); + v_s4 = x[0]; // Ok, was inited before. + EXPECT_POISONED(v_s4 = x[2]); + EXPECT_POISONED(v_s4 = x[1]); + x[2] = 1; // Init this here. Check that after realloc it is poisoned again. + x = (int*)Ident(realloc(x, 2 * sizeof(S4))); + v_s4 = x[0]; // Ok, was inited before. + EXPECT_POISONED(v_s4 = x[1]); + x = (int*)Ident(realloc(x, 3 * sizeof(S4))); + EXPECT_POISONED(v_s4 = x[1]); + EXPECT_POISONED(v_s4 = x[2]); + free(x); +} + +TEST(MemorySanitizer, Calloc) { + S4 *x = (int*)Ident(calloc(1, sizeof(S4))); + v_s4 = *x; // Should not be poisoned. + // EXPECT_EQ(0, *x); + free(x); +} + +TEST(MemorySanitizer, AndOr) { + U4 *p = GetPoisoned<U4>(); + // We poison two bytes in the midle of a 4-byte word to make the test + // correct regardless of endianness. + ((U1*)p)[1] = 0; + ((U1*)p)[2] = 0xff; + v_u4 = *p & 0x00ffff00; + v_u4 = *p & 0x00ff0000; + v_u4 = *p & 0x0000ff00; + EXPECT_POISONED(v_u4 = *p & 0xff000000); + EXPECT_POISONED(v_u4 = *p & 0x000000ff); + EXPECT_POISONED(v_u4 = *p & 0x0000ffff); + EXPECT_POISONED(v_u4 = *p & 0xffff0000); + + v_u4 = *p | 0xff0000ff; + v_u4 = *p | 0xff00ffff; + v_u4 = *p | 0xffff00ff; + EXPECT_POISONED(v_u4 = *p | 0xff000000); + EXPECT_POISONED(v_u4 = *p | 0x000000ff); + EXPECT_POISONED(v_u4 = *p | 0x0000ffff); + EXPECT_POISONED(v_u4 = *p | 0xffff0000); + + EXPECT_POISONED(v_u4 = *GetPoisoned<bool>() & *GetPoisoned<bool>()); +} + +template<class T> +static void testNot(T value, T shadow) { + __msan_partial_poison(&value, &shadow, sizeof(T)); + volatile bool v_T = !value; +} + +TEST(MemorySanitizer, Not) { + testNot<U4>(0x0, 0x0); + testNot<U4>(0xFFFFFFFF, 0x0); + EXPECT_POISONED(testNot<U4>(0xFFFFFFFF, 0xFFFFFFFF)); + testNot<U4>(0xFF000000, 0x0FFFFFFF); + testNot<U4>(0xFF000000, 0x00FFFFFF); + testNot<U4>(0xFF000000, 0x0000FFFF); + testNot<U4>(0xFF000000, 0x00000000); + EXPECT_POISONED(testNot<U4>(0xFF000000, 0xFF000000)); + testNot<U4>(0xFF800000, 0xFF000000); + EXPECT_POISONED(testNot<U4>(0x00008000, 0x00008000)); + + testNot<U1>(0x0, 0x0); + testNot<U1>(0xFF, 0xFE); + testNot<U1>(0xFF, 0x0); + EXPECT_POISONED(testNot<U1>(0xFF, 0xFF)); + + EXPECT_POISONED(testNot<void*>((void*)0xFFFFFF, (void*)(-1))); + testNot<void*>((void*)0xFFFFFF, (void*)(-2)); +} + +TEST(MemorySanitizer, Shift) { + U4 *up = GetPoisoned<U4>(); + ((U1*)up)[0] = 0; + ((U1*)up)[3] = 0xff; + v_u4 = *up >> 30; + v_u4 = *up >> 24; + EXPECT_POISONED(v_u4 = *up >> 23); + EXPECT_POISONED(v_u4 = *up >> 10); + + v_u4 = *up << 30; + v_u4 = *up << 24; + EXPECT_POISONED(v_u4 = *up << 23); + EXPECT_POISONED(v_u4 = *up << 10); + + S4 *sp = (S4*)up; + v_s4 = *sp >> 30; + v_s4 = *sp >> 24; + EXPECT_POISONED(v_s4 = *sp >> 23); + EXPECT_POISONED(v_s4 = *sp >> 10); + + sp = GetPoisoned<S4>(); + ((S1*)sp)[1] = 0; + ((S1*)sp)[2] = 0; + EXPECT_POISONED(v_s4 = *sp >> 31); + + v_s4 = 100; + EXPECT_POISONED(v_s4 = v_s4 >> *GetPoisoned<S4>()); + v_u4 = 100; + EXPECT_POISONED(v_u4 = v_u4 >> *GetPoisoned<S4>()); + v_u4 = 100; + EXPECT_POISONED(v_u4 = v_u4 << *GetPoisoned<S4>()); +} + +NOINLINE static int GetPoisonedZero() { + int *zero = new int; + *zero = 0; + __msan_poison(zero, sizeof(*zero)); + int res = *zero; + delete zero; + return res; +} + +TEST(MemorySanitizer, LoadFromDirtyAddress) { + int *a = new int; + *a = 0; + EXPECT_POISONED(__msan_break_optimization((void*)(U8)a[GetPoisonedZero()])); + delete a; +} + +TEST(MemorySanitizer, StoreToDirtyAddress) { + int *a = new int; + EXPECT_POISONED(a[GetPoisonedZero()] = 0); + __msan_break_optimization(a); + delete a; +} + + +NOINLINE void StackTestFunc() { + S4 p4; + S4 ok4 = 1; + S2 p2; + S2 ok2 = 1; + S1 p1; + S1 ok1 = 1; + __msan_break_optimization(&p4); + __msan_break_optimization(&ok4); + __msan_break_optimization(&p2); + __msan_break_optimization(&ok2); + __msan_break_optimization(&p1); + __msan_break_optimization(&ok1); + + EXPECT_POISONED(v_s4 = p4); + EXPECT_POISONED(v_s2 = p2); + EXPECT_POISONED(v_s1 = p1); + v_s1 = ok1; + v_s2 = ok2; + v_s4 = ok4; +} + +TEST(MemorySanitizer, StackTest) { + StackTestFunc(); +} + +NOINLINE void StackStressFunc() { + int foo[10000]; + __msan_break_optimization(foo); +} + +TEST(MemorySanitizer, DISABLED_StackStressTest) { + for (int i = 0; i < 1000000; i++) + StackStressFunc(); +} + +template<class T> +void TestFloatingPoint() { + static volatile T v; + static T g[100]; + __msan_break_optimization(&g); + T *x = GetPoisoned<T>(); + T *y = GetPoisoned<T>(1); + EXPECT_POISONED(v = *x); + EXPECT_POISONED(v_s8 = *x); + EXPECT_POISONED(v_s4 = *x); + g[0] = *x; + g[1] = *x + *y; + g[2] = *x - *y; + g[3] = *x * *y; +} + +TEST(MemorySanitizer, FloatingPointTest) { + TestFloatingPoint<float>(); + TestFloatingPoint<double>(); +} + +TEST(MemorySanitizer, DynMem) { + S4 x = 0; + S4 *y = GetPoisoned<S4>(); + memcpy(y, &x, g_one * sizeof(S4)); + v_s4 = *y; +} + +static char *DynRetTestStr; + +TEST(MemorySanitizer, DynRet) { + if (!__msan_has_dynamic_component()) return; + ReturnPoisoned<S8>(); + v_s4 = clearenv(); +} + + +TEST(MemorySanitizer, DynRet1) { + if (!__msan_has_dynamic_component()) return; + ReturnPoisoned<S8>(); +} + +struct LargeStruct { + S4 x[10]; +}; + +NOINLINE +LargeStruct LargeRetTest() { + LargeStruct res; + res.x[0] = *GetPoisoned<S4>(); + res.x[1] = *GetPoisoned<S4>(); + res.x[2] = *GetPoisoned<S4>(); + res.x[3] = *GetPoisoned<S4>(); + res.x[4] = *GetPoisoned<S4>(); + res.x[5] = *GetPoisoned<S4>(); + res.x[6] = *GetPoisoned<S4>(); + res.x[7] = *GetPoisoned<S4>(); + res.x[8] = *GetPoisoned<S4>(); + res.x[9] = *GetPoisoned<S4>(); + return res; +} + +TEST(MemorySanitizer, LargeRet) { + LargeStruct a = LargeRetTest(); + EXPECT_POISONED(v_s4 = a.x[0]); + EXPECT_POISONED(v_s4 = a.x[9]); +} + +TEST(MemorySanitizer, fread) { + char *x = new char[32]; + FILE *f = fopen("/proc/self/stat", "r"); + assert(f); + fread(x, 1, 32, f); + v_s1 = x[0]; + v_s1 = x[16]; + v_s1 = x[31]; + fclose(f); + delete x; +} + +TEST(MemorySanitizer, read) { + char *x = new char[32]; + int fd = open("/proc/self/stat", O_RDONLY); + assert(fd > 0); + int sz = read(fd, x, 32); + assert(sz == 32); + v_s1 = x[0]; + v_s1 = x[16]; + v_s1 = x[31]; + close(fd); + delete x; +} + +TEST(MemorySanitizer, pread) { + char *x = new char[32]; + int fd = open("/proc/self/stat", O_RDONLY); + assert(fd > 0); + int sz = pread(fd, x, 32, 0); + assert(sz == 32); + v_s1 = x[0]; + v_s1 = x[16]; + v_s1 = x[31]; + close(fd); + delete x; +} + +// FIXME: fails now. +TEST(MemorySanitizer, DISABLED_ioctl) { + struct winsize ws; + EXPECT_EQ(ioctl(2, TIOCGWINSZ, &ws), 0); + v_s4 = ws.ws_col; +} + +TEST(MemorySanitizer, readlink) { + char *x = new char[1000]; + readlink("/proc/self/exe", x, 1000); + v_s1 = x[0]; + delete [] x; +} + + +TEST(MemorySanitizer, stat) { + struct stat* st = new struct stat; + int res = stat("/proc/self/stat", st); + assert(!res); + v_u8 = st->st_dev; + v_u8 = st->st_mode; + v_u8 = st->st_size; +} + +TEST(MemorySanitizer, statfs) { + struct statfs* st = new struct statfs; + int res = statfs("/", st); + assert(!res); + v_u8 = st->f_type; + v_u8 = st->f_bfree; + v_u8 = st->f_namelen; +} + +TEST(MemorySanitizer, pipe) { + int* pipefd = new int[2]; + int res = pipe(pipefd); + assert(!res); + v_u8 = pipefd[0]; + v_u8 = pipefd[1]; + close(pipefd[0]); + close(pipefd[1]); +} + +TEST(MemorySanitizer, getcwd) { + char path[PATH_MAX + 1]; + char* res = getcwd(path, sizeof(path)); + assert(res); + v_s1 = path[0]; +} + +TEST(MemorySanitizer, realpath) { + const char* relpath = "."; + char path[PATH_MAX + 1]; + char* res = realpath(relpath, path); + assert(res); + v_s1 = path[0]; +} + +TEST(MemorySanitizer, memcpy) { + char* x = new char[2]; + char* y = new char[2]; + x[0] = 1; + x[1] = *GetPoisoned<char>(); + memcpy(y, x, 2); + v_s4 = y[0]; + EXPECT_POISONED(v_s4 = y[1]); +} + +TEST(MemorySanitizer, memmove) { + char* x = new char[2]; + char* y = new char[2]; + x[0] = 1; + x[1] = *GetPoisoned<char>(); + memmove(y, x, 2); + v_s4 = y[0]; + EXPECT_POISONED(v_s4 = y[1]); +} + +TEST(MemorySanitizer, strdup) { + char *x = strdup("zzz"); + v_s1 = *x; + free(x); +} + +template<class T, int size> +void TestOverlapMemmove() { + T *x = new T[size]; + assert(size >= 3); + x[2] = 0; + memmove(x, x + 1, (size - 1) * sizeof(T)); + v_s8 = x[1]; + if (!__msan_has_dynamic_component()) { + // FIXME: under DR we will lose this information + // because accesses in memmove will unpoisin the shadow. + // We need to use our own memove implementation instead of libc's. + EXPECT_POISONED(v_s8 = x[0]); + EXPECT_POISONED(v_s8 = x[2]); + } + delete [] x; +} + +TEST(MemorySanitizer, overlap_memmove) { + TestOverlapMemmove<U1, 10>(); + TestOverlapMemmove<U1, 1000>(); + TestOverlapMemmove<U8, 4>(); + TestOverlapMemmove<U8, 1000>(); +} + +TEST(MemorySanitizer, strcpy) { // NOLINT + char* x = new char[3]; + char* y = new char[3]; + x[0] = 'a'; + x[1] = *GetPoisoned<char>(1, 1); + x[2] = 0; + strcpy(y, x); // NOLINT + v_s4 = y[0]; + EXPECT_POISONED(v_s4 = y[1]); + v_s4 = y[2]; +} + +TEST(MemorySanitizer, strncpy) { // NOLINT + char* x = new char[3]; + char* y = new char[3]; + x[0] = 'a'; + x[1] = *GetPoisoned<char>(1, 1); + x[2] = 0; + strncpy(y, x, 2); // NOLINT + v_s4 = y[0]; + EXPECT_POISONED(v_s4 = y[1]); + EXPECT_POISONED(v_s4 = y[2]); +} + +TEST(MemorySanitizer, strtol) { + char *e; + assert(1 == strtol("1", &e, 10)); + v_s8 = (S8) e; +} + +TEST(MemorySanitizer, strtoll) { + char *e; + assert(1 == strtoll("1", &e, 10)); + v_s8 = (S8) e; +} + +TEST(MemorySanitizer, strtoul) { + char *e; + assert(1 == strtoul("1", &e, 10)); + v_s8 = (S8) e; +} + +TEST(MemorySanitizer, strtoull) { + char *e; + assert(1 == strtoull("1", &e, 10)); + v_s8 = (S8) e; +} + +TEST(MemorySanitizer, strtod) { + char *e; + assert(0 != strtod("1.5", &e)); + v_s8 = (S8) e; +} + +TEST(MemorySanitizer, strtof) { + char *e; + assert(0 != strtof("1.5", &e)); + v_s8 = (S8) e; +} + +TEST(MemorySanitizer, strtold) { + char *e; + assert(0 != strtold("1.5", &e)); + v_s8 = (S8) e; +} + +TEST(MemorySanitizer, sprintf) { // NOLINT + char buff[10]; + __msan_break_optimization(buff); + EXPECT_POISONED(v_s1 = buff[0]); + int res = sprintf(buff, "%d", 1234567); // NOLINT + assert(res == 7); + assert(buff[0] == '1'); + assert(buff[1] == '2'); + assert(buff[2] == '3'); + assert(buff[6] == '7'); + assert(buff[7] == 0); + EXPECT_POISONED(v_s1 = buff[8]); +} + +TEST(MemorySanitizer, snprintf) { + char buff[10]; + __msan_break_optimization(buff); + EXPECT_POISONED(v_s1 = buff[0]); + int res = snprintf(buff, sizeof(buff), "%d", 1234567); + assert(res == 7); + assert(buff[0] == '1'); + assert(buff[1] == '2'); + assert(buff[2] == '3'); + assert(buff[6] == '7'); + assert(buff[7] == 0); + EXPECT_POISONED(v_s1 = buff[8]); +} + +TEST(MemorySanitizer, swprintf) { + wchar_t buff[10]; + assert(sizeof(wchar_t) == 4); + __msan_break_optimization(buff); + EXPECT_POISONED(v_s1 = buff[0]); + int res = swprintf(buff, 9, L"%d", 1234567); + assert(res == 7); + assert(buff[0] == '1'); + assert(buff[1] == '2'); + assert(buff[2] == '3'); + assert(buff[6] == '7'); + assert(buff[7] == 0); + EXPECT_POISONED(v_s4 = buff[8]); +} + +TEST(MemorySanitizer, wcstombs) { + const wchar_t *x = L"abc"; + char buff[10]; + int res = wcstombs(buff, x, 4); + EXPECT_EQ(res, 3); + EXPECT_EQ(buff[0], 'a'); + EXPECT_EQ(buff[1], 'b'); + EXPECT_EQ(buff[2], 'c'); +} + +TEST(MemorySanitizer, gettimeofday) { + struct timeval tv; + struct timezone tz; + __msan_break_optimization(&tv); + __msan_break_optimization(&tz); + assert(sizeof(tv) == 16); + assert(sizeof(tz) == 8); + EXPECT_POISONED(v_s8 = tv.tv_sec); + EXPECT_POISONED(v_s8 = tv.tv_usec); + EXPECT_POISONED(v_s4 = tz.tz_minuteswest); + EXPECT_POISONED(v_s4 = tz.tz_dsttime); + assert(0 == gettimeofday(&tv, &tz)); + v_s8 = tv.tv_sec; + v_s8 = tv.tv_usec; + v_s4 = tz.tz_minuteswest; + v_s4 = tz.tz_dsttime; +} + +TEST(MemorySanitizer, mmap) { + const int size = 4096; + void *p1, *p2; + p1 = mmap(0, size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0); + __msan_poison(p1, size); + munmap(p1, size); + for (int i = 0; i < 1000; i++) { + p2 = mmap(0, size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0); + if (p2 == p1) + break; + else + munmap(p2, size); + } + if (p1 == p2) { + v_s1 = *(char*)p2; + munmap(p2, size); + } +} + +// FIXME: enable and add ecvt. +// FIXME: check why msandr does nt handle fcvt. +TEST(MemorySanitizer, fcvt) { + int a, b; + __msan_break_optimization(&a); + __msan_break_optimization(&b); + EXPECT_POISONED(v_s4 = a); + EXPECT_POISONED(v_s4 = b); + char *str = fcvt(12345.6789, 10, &a, &b); + v_s4 = a; + v_s4 = b; +} + +TEST(MemorySanitizer, LoadUnpoisoned) { + S8 s = *GetPoisoned<S8>(); + EXPECT_POISONED(v_s8 = s); + S8 safe = *GetPoisoned<S8>(); + __msan_load_unpoisoned(&s, sizeof(s), &safe); + v_s8 = safe; +} + +struct StructWithDtor { + ~StructWithDtor(); +}; + +NOINLINE StructWithDtor::~StructWithDtor() { + __msan_break_optimization(0); +} + +NOINLINE void ExpectGood(int a) { v_s4 = a; } +NOINLINE void ExpectPoisoned(int a) { + EXPECT_POISONED(v_s4 = a); +} + +TEST(MemorySanitizer, Invoke) { + StructWithDtor s; // Will cause the calls to become invokes. + ExpectGood(0); + ExpectPoisoned(*GetPoisoned<int>()); + ExpectGood(0); + ExpectPoisoned(*GetPoisoned<int>()); + EXPECT_POISONED(v_s4 = ReturnPoisoned<S4>()); +} + +TEST(MemorySanitizer, ptrtoint) { + // Test that shadow is propagated through pointer-to-integer conversion. + void* p = (void*)0xABCD; + __msan_poison(((char*)&p) + 1, sizeof(p)); + v_u1 = (((uptr)p) & 0xFF) == 0; + + void* q = (void*)0xABCD; + __msan_poison(&q, sizeof(q) - 1); + EXPECT_POISONED(v_u1 = (((uptr)q) & 0xFF) == 0); +} + +static void vaargsfn2(int guard, ...) { + va_list vl; + va_start(vl, guard); + v_s4 = va_arg(vl, int); + v_s4 = va_arg(vl, int); + v_s4 = va_arg(vl, int); + EXPECT_POISONED(v_d = va_arg(vl, double)); + va_end(vl); +} + +static void vaargsfn(int guard, ...) { + va_list vl; + va_start(vl, guard); + v_s4 = va_arg(vl, int); + EXPECT_POISONED(v_s4 = va_arg(vl, int)); + // The following call will overwrite __msan_param_tls. + // Checks after it test that arg shadow was somehow saved across the call. + vaargsfn2(1, 2, 3, 4, *GetPoisoned<double>()); + v_s4 = va_arg(vl, int); + EXPECT_POISONED(v_s4 = va_arg(vl, int)); + va_end(vl); +} + +TEST(MemorySanitizer, VAArgTest) { + int* x = GetPoisoned<int>(); + int* y = GetPoisoned<int>(4); + vaargsfn(1, 13, *x, 42, *y); +} + +static void vaargsfn_many(int guard, ...) { + va_list vl; + va_start(vl, guard); + v_s4 = va_arg(vl, int); + EXPECT_POISONED(v_s4 = va_arg(vl, int)); + v_s4 = va_arg(vl, int); + v_s4 = va_arg(vl, int); + v_s4 = va_arg(vl, int); + v_s4 = va_arg(vl, int); + v_s4 = va_arg(vl, int); + v_s4 = va_arg(vl, int); + v_s4 = va_arg(vl, int); + EXPECT_POISONED(v_s4 = va_arg(vl, int)); + va_end(vl); +} + +TEST(MemorySanitizer, VAArgManyTest) { + int* x = GetPoisoned<int>(); + int* y = GetPoisoned<int>(4); + vaargsfn_many(1, 2, *x, 3, 4, 5, 6, 7, 8, 9, *y); +} + +static void vaargsfn_pass2(va_list vl) { + v_s4 = va_arg(vl, int); + v_s4 = va_arg(vl, int); + EXPECT_POISONED(v_s4 = va_arg(vl, int)); +} + +static void vaargsfn_pass(int guard, ...) { + va_list vl; + va_start(vl, guard); + EXPECT_POISONED(v_s4 = va_arg(vl, int)); + vaargsfn_pass2(vl); + va_end(vl); +} + +TEST(MemorySanitizer, VAArgPass) { + int* x = GetPoisoned<int>(); + int* y = GetPoisoned<int>(4); + vaargsfn_pass(1, *x, 2, 3, *y); +} + +static void vaargsfn_copy2(va_list vl) { + v_s4 = va_arg(vl, int); + EXPECT_POISONED(v_s4 = va_arg(vl, int)); +} + +static void vaargsfn_copy(int guard, ...) { + va_list vl; + va_start(vl, guard); + v_s4 = va_arg(vl, int); + EXPECT_POISONED(v_s4 = va_arg(vl, int)); + va_list vl2; + va_copy(vl2, vl); + vaargsfn_copy2(vl2); + v_s4 = va_arg(vl, int); + EXPECT_POISONED(v_s4 = va_arg(vl, int)); + va_end(vl); +} + +TEST(MemorySanitizer, VAArgCopy) { + int* x = GetPoisoned<int>(); + int* y = GetPoisoned<int>(4); + vaargsfn_copy(1, 2, *x, 3, *y); +} + +static void vaargsfn_ptr(int guard, ...) { + va_list vl; + va_start(vl, guard); + v_p = va_arg(vl, int*); + EXPECT_POISONED(v_p = va_arg(vl, int*)); + v_p = va_arg(vl, int*); + EXPECT_POISONED(v_p = va_arg(vl, double*)); + va_end(vl); +} + +TEST(MemorySanitizer, VAArgPtr) { + int** x = GetPoisoned<int*>(); + double** y = GetPoisoned<double*>(8); + int z; + vaargsfn_ptr(1, &z, *x, &z, *y); +} + +static void vaargsfn_overflow(int guard, ...) { + va_list vl; + va_start(vl, guard); + v_s4 = va_arg(vl, int); + v_s4 = va_arg(vl, int); + EXPECT_POISONED(v_s4 = va_arg(vl, int)); + v_s4 = va_arg(vl, int); + v_s4 = va_arg(vl, int); + v_s4 = va_arg(vl, int); + + v_d = va_arg(vl, double); + v_d = va_arg(vl, double); + v_d = va_arg(vl, double); + EXPECT_POISONED(v_d = va_arg(vl, double)); + v_d = va_arg(vl, double); + EXPECT_POISONED(v_p = va_arg(vl, int*)); + v_d = va_arg(vl, double); + v_d = va_arg(vl, double); + + EXPECT_POISONED(v_s4 = va_arg(vl, int)); + EXPECT_POISONED(v_d = va_arg(vl, double)); + EXPECT_POISONED(v_p = va_arg(vl, int*)); + + v_s4 = va_arg(vl, int); + v_d = va_arg(vl, double); + v_p = va_arg(vl, int*); + + EXPECT_POISONED(v_s4 = va_arg(vl, int)); + EXPECT_POISONED(v_d = va_arg(vl, double)); + EXPECT_POISONED(v_p = va_arg(vl, int*)); + + va_end(vl); +} + +TEST(MemorySanitizer, VAArgOverflow) { + int* x = GetPoisoned<int>(); + double* y = GetPoisoned<double>(8); + int** p = GetPoisoned<int*>(16); + int z; + vaargsfn_overflow(1, + 1, 2, *x, 4, 5, 6, + 1.1, 2.2, 3.3, *y, 5.5, *p, 7.7, 8.8, + // the following args will overflow for sure + *x, *y, *p, + 7, 9.9, &z, + *x, *y, *p); +} + +static void vaargsfn_tlsoverwrite2(int guard, ...) { + va_list vl; + va_start(vl, guard); + v_s4 = va_arg(vl, int); + va_end(vl); +} + +static void vaargsfn_tlsoverwrite(int guard, ...) { + // This call will overwrite TLS contents unless it's backed up somewhere. + vaargsfn_tlsoverwrite2(2, 42); + va_list vl; + va_start(vl, guard); + EXPECT_POISONED(v_s4 = va_arg(vl, int)); + va_end(vl); +} + +TEST(MemorySanitizer, VAArgTLSOverwrite) { + int* x = GetPoisoned<int>(); + vaargsfn_tlsoverwrite(1, *x); +} + +struct StructByVal { + int a, b, c, d, e, f; +}; + +NOINLINE void StructByValTestFunc(struct StructByVal s) { + v_s4 = s.a; + EXPECT_POISONED(v_s4 = s.b); + v_s4 = s.c; + EXPECT_POISONED(v_s4 = s.d); + v_s4 = s.e; + EXPECT_POISONED(v_s4 = s.f); +} + +NOINLINE void StructByValTestFunc1(struct StructByVal s) { + StructByValTestFunc(s); +} + +NOINLINE void StructByValTestFunc2(int z, struct StructByVal s) { + StructByValTestFunc(s); +} + +TEST(MemorySanitizer, StructByVal) { + // Large aggregates are passed as "byval" pointer argument in LLVM. + struct StructByVal s; + s.a = 1; + s.b = *GetPoisoned<int>(); + s.c = 2; + s.d = *GetPoisoned<int>(); + s.e = 3; + s.f = *GetPoisoned<int>(); + StructByValTestFunc(s); + StructByValTestFunc1(s); + StructByValTestFunc2(0, s); +} + + +#if MSAN_HAS_M128 +NOINLINE __m128i m128Eq(__m128i *a, __m128i *b) { return *a == *b; } +NOINLINE __m128i m128Lt(__m128i *a, __m128i *b) { return *a < *b; } +TEST(MemorySanitizer, m128) { + __m128i a = _mm_set1_epi16(0x1234); + __m128i b = _mm_set1_epi16(0x7890); + v_m128 = m128Eq(&a, &b); + v_m128 = m128Lt(&a, &b); +} +// FIXME: add more tests for __m128i. +#endif // MSAN_HAS_M128 + +// We should not complain when copying this poisoned hole. +struct StructWithHole { + U4 a; + // 4-byte hole. + U8 b; +}; + +NOINLINE StructWithHole ReturnStructWithHole() { + StructWithHole res; + __msan_poison(&res, sizeof(res)); + res.a = 1; + res.b = 2; + return res; +} + +TEST(MemorySanitizer, StructWithHole) { + StructWithHole a = ReturnStructWithHole(); + __msan_break_optimization(&a); +} + +template <class T> +NOINLINE T ReturnStruct() { + T res; + __msan_poison(&res, sizeof(res)); + res.a = 1; + return res; +} + +template <class T> +NOINLINE void TestReturnStruct() { + T s1 = ReturnStruct<T>(); + v_s4 = s1.a; + EXPECT_POISONED(v_s4 = s1.b); +} + +struct SSS1 { + int a, b, c; +}; +struct SSS2 { + int b, a, c; +}; +struct SSS3 { + int b, c, a; +}; +struct SSS4 { + int c, b, a; +}; + +struct SSS5 { + int a; + float b; +}; +struct SSS6 { + int a; + double b; +}; +struct SSS7 { + S8 b; + int a; +}; +struct SSS8 { + S2 b; + S8 a; +}; + +TEST(MemorySanitizer, IntStruct3) { + TestReturnStruct<SSS1>(); + TestReturnStruct<SSS2>(); + TestReturnStruct<SSS3>(); + TestReturnStruct<SSS4>(); + TestReturnStruct<SSS5>(); + TestReturnStruct<SSS6>(); + TestReturnStruct<SSS7>(); + TestReturnStruct<SSS8>(); +} + +struct LongStruct { + U1 a1, b1; + U2 a2, b2; + U4 a4, b4; + U8 a8, b8; +}; + +NOINLINE LongStruct ReturnLongStruct1() { + LongStruct res; + __msan_poison(&res, sizeof(res)); + res.a1 = res.a2 = res.a4 = res.a8 = 111; + // leaves b1, .., b8 poisoned. + return res; +} + +NOINLINE LongStruct ReturnLongStruct2() { + LongStruct res; + __msan_poison(&res, sizeof(res)); + res.b1 = res.b2 = res.b4 = res.b8 = 111; + // leaves a1, .., a8 poisoned. + return res; +} + +TEST(MemorySanitizer, LongStruct) { + LongStruct s1 = ReturnLongStruct1(); + __msan_print_shadow(&s1, sizeof(s1)); + v_u1 = s1.a1; + v_u2 = s1.a2; + v_u4 = s1.a4; + v_u8 = s1.a8; + + EXPECT_POISONED(v_u1 = s1.b1); + EXPECT_POISONED(v_u2 = s1.b2); + EXPECT_POISONED(v_u4 = s1.b4); + EXPECT_POISONED(v_u8 = s1.b8); + + LongStruct s2 = ReturnLongStruct2(); + __msan_print_shadow(&s2, sizeof(s2)); + v_u1 = s2.b1; + v_u2 = s2.b2; + v_u4 = s2.b4; + v_u8 = s2.b8; + + EXPECT_POISONED(v_u1 = s2.a1); + EXPECT_POISONED(v_u2 = s2.a2); + EXPECT_POISONED(v_u4 = s2.a4); + EXPECT_POISONED(v_u8 = s2.a8); +} + +TEST(MemorySanitizer, getrlimit) { + struct rlimit limit; + __msan_poison(&limit, sizeof(limit)); + int result = getrlimit(RLIMIT_DATA, &limit); + assert(result == 0); + volatile rlim_t t; + t = limit.rlim_cur; + t = limit.rlim_max; +} + +TEST(MemorySanitizer, getrusage) { + struct rusage usage; + __msan_poison(&usage, sizeof(usage)); + int result = getrusage(RUSAGE_SELF, &usage); + assert(result == 0); + volatile struct timeval t; + v_u8 = usage.ru_utime.tv_sec; + v_u8 = usage.ru_utime.tv_usec; + v_u8 = usage.ru_stime.tv_sec; + v_u8 = usage.ru_stime.tv_usec; + v_s8 = usage.ru_maxrss; + v_s8 = usage.ru_minflt; + v_s8 = usage.ru_majflt; + v_s8 = usage.ru_inblock; + v_s8 = usage.ru_oublock; + v_s8 = usage.ru_nvcsw; + v_s8 = usage.ru_nivcsw; +} + +static void dladdr_testfn() {} + +TEST(MemorySanitizer, dladdr) { + Dl_info info; + __msan_poison(&info, sizeof(info)); + int result = dladdr((const void*)dladdr_testfn, &info); + assert(result != 0); + v_u8 = (unsigned long)info.dli_fname; + if (info.dli_fname) + v_u8 = strlen(info.dli_fname); + v_u8 = (unsigned long)info.dli_fbase; + v_u8 = (unsigned long)info.dli_sname; + if (info.dli_sname) + v_u8 = strlen(info.dli_sname); + v_u8 = (unsigned long)info.dli_saddr; +} + +TEST(MemorySanitizer, scanf) { + const char *input = "42 hello"; + int* d = new int; + char* s = new char[7]; + int res = sscanf(input, "%d %5s", d, s); + printf("res %d\n", res); + assert(res == 2); + v_s4 = *d; + v_u1 = s[0]; + v_u1 = s[1]; + v_u1 = s[2]; + v_u1 = s[3]; + v_u1 = s[4]; + v_u1 = s[5]; + EXPECT_POISONED(v_u1 = s[6]); + delete s; + delete d; +} + +static void* SimpleThread_threadfn(void* data) { + return new int; +} + +TEST(MemorySanitizer, SimpleThread) { + pthread_t t; + void* p; + int res = pthread_create(&t, NULL, SimpleThread_threadfn, NULL); + assert(!res); + res = pthread_join(t, &p); + assert(!res); + if (!__msan_has_dynamic_component()) // FIXME: intercept pthread_join (?). + __msan_unpoison(&p, sizeof(p)); + delete (int*)p; +} + +TEST(MemorySanitizer, uname) { + struct utsname u; + int res = uname(&u); + assert(!res); + v_u8 = strlen(u.sysname); + v_u8 = strlen(u.nodename); + v_u8 = strlen(u.release); + v_u8 = strlen(u.version); + v_u8 = strlen(u.machine); +} + +template<class T> +static void testSlt(T value, T shadow) { + __msan_partial_poison(&value, &shadow, sizeof(T)); + volatile bool zzz = true; + // This "|| zzz" trick somehow makes LLVM emit "icmp slt" instead of + // a shift-and-trunc to get at the highest bit. + volatile bool v_T = value < 0 || zzz; +} + +TEST(MemorySanitizer, SignedCompareWithZero) { + testSlt<S4>(0xF, 0xF); + testSlt<S4>(0xF, 0xFF); + testSlt<S4>(0xF, 0xFFFFFF); + testSlt<S4>(0xF, 0x7FFFFFF); + EXPECT_POISONED(testSlt<S4>(0xF, 0x80FFFFFF)); + EXPECT_POISONED(testSlt<S4>(0xF, 0xFFFFFFFF)); +} + +extern "C" { +NOINLINE void ZZZZZZZZZZZZZZ() { + __msan_break_optimization(0); + + // v_s1 = ReturnPoisoned<S1>(); + // a_s8[g_zero] = *GetPoisoned<S8>() - 1; + // v_s4 = a_s4[g_zero]; + __msan_break_optimization(0); +} +} + +TEST(MemorySanitizer, ZZZTest) { + ZZZZZZZZZZZZZZ(); +} + +TEST(MemorySanitizerDr, StoreInDSOTest) { + if (!__msan_has_dynamic_component()) return; + char* s = new char[10]; + dso_memfill(s, 9); + v_s1 = s[5]; + EXPECT_POISONED(v_s1 = s[9]); +} + +int return_poisoned_int() { + return ReturnPoisoned<U8>(); +} + +TEST(MemorySanitizerDr, ReturnFromDSOTest) { + if (!__msan_has_dynamic_component()) return; + v_u8 = dso_callfn(return_poisoned_int); +} + +NOINLINE int TrashParamTLS(long long x, long long y, long long z) { //NOLINT + EXPECT_POISONED(v_s8 = x); + EXPECT_POISONED(v_s8 = y); + EXPECT_POISONED(v_s8 = z); + return 0; +} + +static int CheckParamTLS(long long x, long long y, long long z) { //NOLINT + v_s8 = x; + v_s8 = y; + v_s8 = z; + return 0; +} + +TEST(MemorySanitizerDr, CallFromDSOTest) { + if (!__msan_has_dynamic_component()) return; + S8* x = GetPoisoned<S8>(); + S8* y = GetPoisoned<S8>(); + S8* z = GetPoisoned<S8>(); + v_s4 = TrashParamTLS(*x, *y, *z); + v_u8 = dso_callfn1(CheckParamTLS); +} + +static void StackStoreInDSOFn(int* x, int* y) { + v_s4 = *x; + v_s4 = *y; +} + +TEST(MemorySanitizerDr, StackStoreInDSOTest) { + if (!__msan_has_dynamic_component()) return; + dso_stack_store(StackStoreInDSOFn, 1); +} + +TEST(MemorySanitizerOrigins, SetGet) { + EXPECT_EQ(TrackingOrigins(), __msan_get_track_origins()); + if (!TrackingOrigins()) return; + int x; + __msan_set_origin(&x, sizeof(x), 1234); + EXPECT_EQ(1234, __msan_get_origin(&x)); + __msan_set_origin(&x, sizeof(x), 5678); + EXPECT_EQ(5678, __msan_get_origin(&x)); + __msan_set_origin(&x, sizeof(x), 0); + EXPECT_EQ(0, __msan_get_origin(&x)); +} + +namespace { +struct S { + U4 dummy; + U2 a; + U2 b; +}; + +// http://code.google.com/p/memory-sanitizer/issues/detail?id=6 +TEST(MemorySanitizerOrigins, DISABLED_InitializedStoreDoesNotChangeOrigin) { + if (!TrackingOrigins()) return; + + S s; + u32 origin = rand(); // NOLINT + s.a = *GetPoisonedO<U2>(0, origin); + EXPECT_EQ(origin, __msan_get_origin(&s.a)); + EXPECT_EQ(origin, __msan_get_origin(&s.b)); + + s.b = 42; + EXPECT_EQ(origin, __msan_get_origin(&s.a)); + EXPECT_EQ(origin, __msan_get_origin(&s.b)); +} +} // namespace + +template<class T, class BinaryOp> +INLINE +void BinaryOpOriginTest(BinaryOp op) { + u32 ox = rand(); //NOLINT + u32 oy = rand(); //NOLINT + T *x = GetPoisonedO<T>(0, ox, 0); + T *y = GetPoisonedO<T>(1, oy, 0); + T *z = GetPoisonedO<T>(2, 0, 0); + + *z = op(*x, *y); + u32 origin = __msan_get_origin(z); + EXPECT_POISONED_O(v_s8 = *z, origin); + EXPECT_EQ(true, origin == ox || origin == oy); + + // y is poisoned, x is not. + *x = 10101; + *y = *GetPoisonedO<T>(1, oy); + __msan_break_optimization(x); + __msan_set_origin(z, sizeof(*z), 0); + *z = op(*x, *y); + EXPECT_POISONED_O(v_s8 = *z, oy); + EXPECT_EQ(__msan_get_origin(z), oy); + + // x is poisoned, y is not. + *x = *GetPoisonedO<T>(0, ox); + *y = 10101010; + __msan_break_optimization(y); + __msan_set_origin(z, sizeof(*z), 0); + *z = op(*x, *y); + EXPECT_POISONED_O(v_s8 = *z, ox); + EXPECT_EQ(__msan_get_origin(z), ox); +} + +template<class T> INLINE T XOR(const T &a, const T&b) { return a ^ b; } +template<class T> INLINE T ADD(const T &a, const T&b) { return a + b; } +template<class T> INLINE T SUB(const T &a, const T&b) { return a - b; } +template<class T> INLINE T MUL(const T &a, const T&b) { return a * b; } +template<class T> INLINE T AND(const T &a, const T&b) { return a & b; } +template<class T> INLINE T OR (const T &a, const T&b) { return a | b; } + +TEST(MemorySanitizerOrigins, BinaryOp) { + if (!TrackingOrigins()) return; + BinaryOpOriginTest<S8>(XOR<S8>); + BinaryOpOriginTest<U8>(ADD<U8>); + BinaryOpOriginTest<S4>(SUB<S4>); + BinaryOpOriginTest<S4>(MUL<S4>); + BinaryOpOriginTest<U4>(OR<U4>); + BinaryOpOriginTest<U4>(AND<U4>); + BinaryOpOriginTest<double>(ADD<U4>); + BinaryOpOriginTest<float>(ADD<S4>); + BinaryOpOriginTest<double>(ADD<double>); + BinaryOpOriginTest<float>(ADD<double>); +} + +TEST(MemorySanitizerOrigins, Unary) { + if (!TrackingOrigins()) return; + EXPECT_POISONED_O(v_s8 = *GetPoisonedO<S8>(0, __LINE__), __LINE__); + EXPECT_POISONED_O(v_s4 = *GetPoisonedO<S8>(0, __LINE__), __LINE__); + EXPECT_POISONED_O(v_s2 = *GetPoisonedO<S8>(0, __LINE__), __LINE__); + EXPECT_POISONED_O(v_s1 = *GetPoisonedO<S8>(0, __LINE__), __LINE__); + + EXPECT_POISONED_O(v_s8 = *GetPoisonedO<S4>(0, __LINE__), __LINE__); + EXPECT_POISONED_O(v_s4 = *GetPoisonedO<S4>(0, __LINE__), __LINE__); + EXPECT_POISONED_O(v_s2 = *GetPoisonedO<S4>(0, __LINE__), __LINE__); + EXPECT_POISONED_O(v_s1 = *GetPoisonedO<S4>(0, __LINE__), __LINE__); + + EXPECT_POISONED_O(v_s8 = *GetPoisonedO<U4>(0, __LINE__), __LINE__); + EXPECT_POISONED_O(v_s4 = *GetPoisonedO<U4>(0, __LINE__), __LINE__); + EXPECT_POISONED_O(v_s2 = *GetPoisonedO<U4>(0, __LINE__), __LINE__); + EXPECT_POISONED_O(v_s1 = *GetPoisonedO<U4>(0, __LINE__), __LINE__); + + EXPECT_POISONED_O(v_u8 = *GetPoisonedO<S4>(0, __LINE__), __LINE__); + EXPECT_POISONED_O(v_u4 = *GetPoisonedO<S4>(0, __LINE__), __LINE__); + EXPECT_POISONED_O(v_u2 = *GetPoisonedO<S4>(0, __LINE__), __LINE__); + EXPECT_POISONED_O(v_u1 = *GetPoisonedO<S4>(0, __LINE__), __LINE__); + + EXPECT_POISONED_O(v_p = (void*)*GetPoisonedO<S8>(0, __LINE__), __LINE__); + EXPECT_POISONED_O(v_u8 = (U8)*GetPoisonedO<void*>(0, __LINE__), __LINE__); +} + +TEST(MemorySanitizerOrigins, EQ) { + if (!TrackingOrigins()) return; + EXPECT_POISONED_O(v_u1 = *GetPoisonedO<S4>(0, __LINE__) <= 11, __LINE__); + EXPECT_POISONED_O(v_u1 = *GetPoisonedO<S4>(0, __LINE__) == 11, __LINE__); + EXPECT_POISONED_O(v_u1 = *GetPoisonedO<float>(0, __LINE__) == 1.1, __LINE__); +} + +TEST(MemorySanitizerOrigins, DIV) { + if (!TrackingOrigins()) return; + EXPECT_POISONED_O(v_u8 = *GetPoisonedO<U8>(0, __LINE__) / 100, __LINE__); + EXPECT_POISONED_O(v_s4 = 100 / *GetPoisonedO<S4>(0, __LINE__, 1), __LINE__); +} + +TEST(MemorySanitizerOrigins, SHIFT) { + if (!TrackingOrigins()) return; + EXPECT_POISONED_O(v_u8 = *GetPoisonedO<U8>(0, __LINE__) >> 10, __LINE__); + EXPECT_POISONED_O(v_s8 = *GetPoisonedO<S8>(0, __LINE__) >> 10, __LINE__); + EXPECT_POISONED_O(v_s8 = *GetPoisonedO<S8>(0, __LINE__) << 10, __LINE__); + EXPECT_POISONED_O(v_u8 = 10U << *GetPoisonedO<U8>(0, __LINE__), __LINE__); + EXPECT_POISONED_O(v_s8 = -10 >> *GetPoisonedO<S8>(0, __LINE__), __LINE__); + EXPECT_POISONED_O(v_s8 = -10 << *GetPoisonedO<S8>(0, __LINE__), __LINE__); +} + +template<class T, int N> +void MemCpyTest() { + int ox = __LINE__; + T *x = new T[N]; + T *y = new T[N]; + T *z = new T[N]; + __msan_poison(x, N * sizeof(T)); + __msan_set_origin(x, N * sizeof(T), ox); + __msan_set_origin(y, N * sizeof(T), 777777); + __msan_set_origin(z, N * sizeof(T), 888888); + v_p = x; + memcpy(y, v_p, N * sizeof(T)); + EXPECT_POISONED_O(v_s1 = y[0], ox); + EXPECT_POISONED_O(v_s1 = y[N/2], ox); + EXPECT_POISONED_O(v_s1 = y[N-1], ox); + v_p = x; + memmove(z, v_p, N * sizeof(T)); + EXPECT_POISONED_O(v_s1 = z[0], ox); + EXPECT_POISONED_O(v_s1 = z[N/2], ox); + EXPECT_POISONED_O(v_s1 = z[N-1], ox); +} + +TEST(MemorySanitizerOrigins, LargeMemCpy) { + if (!TrackingOrigins()) return; + MemCpyTest<U1, 10000>(); + MemCpyTest<U8, 10000>(); +} + +TEST(MemorySanitizerOrigins, SmallMemCpy) { + if (!TrackingOrigins()) return; + MemCpyTest<U8, 1>(); + MemCpyTest<U8, 2>(); + MemCpyTest<U8, 3>(); +} + +TEST(MemorySanitizerOrigins, Select) { + if (!TrackingOrigins()) return; + v_s8 = g_one ? 1 : *GetPoisonedO<S4>(0, __LINE__); + EXPECT_POISONED_O(v_s8 = *GetPoisonedO<S4>(0, __LINE__), __LINE__); + S4 x; + __msan_break_optimization(&x); + x = g_1 ? *GetPoisonedO<S4>(0, __LINE__) : 0; + + EXPECT_POISONED_O(v_s8 = g_1 ? *GetPoisonedO<S4>(0, __LINE__) : 1, __LINE__); + EXPECT_POISONED_O(v_s8 = g_0 ? 1 : *GetPoisonedO<S4>(0, __LINE__), __LINE__); +} + +extern "C" +NOINLINE void AllocaTOTest() { + int ar[100]; + __msan_break_optimization(ar); + v_s8 = ar[10]; + // fprintf(stderr, "Descr: %s\n", + // __msan_get_origin_descr_if_stack(__msan_get_origin_tls())); +} + +TEST(MemorySanitizerOrigins, Alloca) { + if (!TrackingOrigins()) return; + EXPECT_POISONED_S(AllocaTOTest(), "ar@AllocaTOTest"); + EXPECT_POISONED_S(AllocaTOTest(), "ar@AllocaTOTest"); + EXPECT_POISONED_S(AllocaTOTest(), "ar@AllocaTOTest"); + EXPECT_POISONED_S(AllocaTOTest(), "ar@AllocaTOTest"); +} + +// FIXME: replace with a lit-like test. +TEST(MemorySanitizerOrigins, DISABLED_AllocaDeath) { + if (!TrackingOrigins()) return; + EXPECT_DEATH(AllocaTOTest(), "ORIGIN: stack allocation: ar@AllocaTOTest"); +} + +NOINLINE int RetvalOriginTest(u32 origin) { + int *a = new int; + __msan_break_optimization(a); + __msan_set_origin(a, sizeof(*a), origin); + int res = *a; + delete a; + return res; +} + +TEST(MemorySanitizerOrigins, Retval) { + if (!TrackingOrigins()) return; + EXPECT_POISONED_O(v_s4 = RetvalOriginTest(__LINE__), __LINE__); +} + +NOINLINE void ParamOriginTest(int param, u32 origin) { + EXPECT_POISONED_O(v_s4 = param, origin); +} + +TEST(MemorySanitizerOrigins, Param) { + if (!TrackingOrigins()) return; + int *a = new int; + u32 origin = __LINE__; + __msan_break_optimization(a); + __msan_set_origin(a, sizeof(*a), origin); + ParamOriginTest(*a, origin); + delete a; +} + +TEST(MemorySanitizerOrigins, Invoke) { + if (!TrackingOrigins()) return; + StructWithDtor s; // Will cause the calls to become invokes. + EXPECT_POISONED_O(v_s4 = RetvalOriginTest(__LINE__), __LINE__); +} + +TEST(MemorySanitizerOrigins, strlen) { + S8 alignment; + __msan_break_optimization(&alignment); + char x[4] = {'a', 'b', 0, 0}; + __msan_poison(&x[2], 1); + u32 origin = __LINE__; + __msan_set_origin(x, sizeof(x), origin); + EXPECT_POISONED_O(v_s4 = strlen(x), origin); +} + +TEST(MemorySanitizerOrigins, wcslen) { + wchar_t w[3] = {'a', 'b', 0}; + u32 origin = __LINE__; + __msan_set_origin(w, sizeof(w), origin); + __msan_poison(&w[2], sizeof(wchar_t)); + EXPECT_POISONED_O(v_s4 = wcslen(w), origin); +} + +#if MSAN_HAS_M128 +TEST(MemorySanitizerOrigins, StoreIntrinsic) { + __m128 x, y; + u32 origin = __LINE__; + __msan_set_origin(&x, sizeof(x), origin); + __msan_poison(&x, sizeof(x)); + __builtin_ia32_storeups((float*)&y, x); + EXPECT_POISONED_O(v_m128 = y, origin); +} +#endif + +NOINLINE void RecursiveMalloc(int depth) { + static int count; + count++; + if ((count % (1024 * 1024)) == 0) + printf("RecursiveMalloc: %d\n", count); + int *x1 = new int; + int *x2 = new int; + __msan_break_optimization(x1); + __msan_break_optimization(x2); + if (depth > 0) { + RecursiveMalloc(depth-1); + RecursiveMalloc(depth-1); + } + delete x1; + delete x2; +} + +TEST(MemorySanitizerStress, DISABLED_MallocStackTrace) { + RecursiveMalloc(22); +} + +int main(int argc, char **argv) { + __msan_set_poison_in_malloc(1); + testing::InitGoogleTest(&argc, argv); + int res = RUN_ALL_TESTS(); + return res; +} diff --git a/lib/msan/tests/msandr_test_so.cc b/lib/msan/tests/msandr_test_so.cc new file mode 100644 index 000000000000..fddd8ded8964 --- /dev/null +++ b/lib/msan/tests/msandr_test_so.cc @@ -0,0 +1,36 @@ +//===-- msandr_test_so.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 MemorySanitizer. +// +// MemorySanitizer unit tests. +//===----------------------------------------------------------------------===// + +#include "msandr_test_so.h" + +void dso_memfill(char* s, unsigned n) { + for (unsigned i = 0; i < n; ++i) + s[i] = i; +} + +int dso_callfn(int (*fn)(void)) { + volatile int x = fn(); + return x; +} + +int dso_callfn1(int (*fn)(long long, long long, long long)) { //NOLINT + volatile int x = fn(1, 2, 3); + return x; +} + +int dso_stack_store(void (*fn)(int*, int*), int x) { + int y = x + 1; + fn(&x, &y); + return y; +} diff --git a/lib/msan/tests/msandr_test_so.h b/lib/msan/tests/msandr_test_so.h new file mode 100644 index 000000000000..6119542ee50e --- /dev/null +++ b/lib/msan/tests/msandr_test_so.h @@ -0,0 +1,23 @@ +//===-- msandr_test_so.h ----------------------------------------*- C++ -*-===// +// +// 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 MemorySanitizer. +// +// MemorySanitizer unit tests. +//===----------------------------------------------------------------------===// + +#ifndef MSANDR_MSANDR_TEST_SO_H +#define MSANDR_MSANDR_TEST_SO_H + +void dso_memfill(char* s, unsigned n); +int dso_callfn(int (*fn)(void)); +int dso_callfn1(int (*fn)(long long, long long, long long)); //NOLINT +int dso_stack_store(void (*fn)(int*, int*), int x); + +#endif diff --git a/lib/profile/GCDAProfiling.c b/lib/profile/GCDAProfiling.c index 8f92a915484b..7c52a1740999 100644 --- a/lib/profile/GCDAProfiling.c +++ b/lib/profile/GCDAProfiling.c @@ -66,6 +66,24 @@ static void write_string(const char *s) { fwrite("\0\0\0\0", 4 - (strlen(s) % 4), 1, output_file); } +static uint32_t read_int32() { + uint32_t tmp; + + if (fread(&tmp, 1, 4, output_file) != 4) + return (uint32_t)-1; + + return tmp; +} + +static uint64_t read_int64() { + uint64_t tmp; + + if (fread(&tmp, 1, 8, output_file) != 8) + return (uint64_t)-1; + + return tmp; +} + static char *mangle_filename(const char *orig_filename) { char *filename = 0; int prefix_len = 0; @@ -129,15 +147,23 @@ static void recursive_mkdir(char *filename) { */ void llvm_gcda_start_file(const char *orig_filename) { char *filename = mangle_filename(orig_filename); - output_file = fopen(filename, "w+b"); + + /* Try just opening the file. */ + output_file = fopen(filename, "r+b"); if (!output_file) { - recursive_mkdir(filename); + /* Try opening the file, creating it if necessary. */ output_file = fopen(filename, "w+b"); if (!output_file) { - fprintf(stderr, "profiling:%s: cannot open\n", filename); - free(filename); - return; + /* Try creating the directories first then opening the file. */ + recursive_mkdir(filename); + output_file = fopen(filename, "w+b"); + if (!output_file) { + /* Bah! It's hopeless. */ + fprintf(stderr, "profiling:%s: cannot open\n", filename); + free(filename); + return; + } } } @@ -148,11 +174,11 @@ void llvm_gcda_start_file(const char *orig_filename) { fwrite("adcg*404MVLL", 12, 1, output_file); #endif + free(filename); + #ifdef DEBUG_GCDAPROFILING - printf("llvmgcda: [%s]\n", orig_filename); + fprintf(stderr, "llvmgcda: [%s]\n", orig_filename); #endif - - free(filename); } /* Given an array of pointers to counters (counters), increment the n-th one, @@ -175,14 +201,14 @@ void llvm_gcda_increment_indirect_counter(uint32_t *predecessor, #ifdef DEBUG_GCDAPROFILING else fprintf(stderr, - "llvmgcda: increment_indirect_counter counters=%x, pred=%u\n", - state_table_row, *predecessor); + "llvmgcda: increment_indirect_counter counters=%08llx, pred=%u\n", + *counter, *predecessor); #endif } void llvm_gcda_emit_function(uint32_t ident, const char *function_name) { #ifdef DEBUG_GCDAPROFILING - printf("llvmgcda: function id=%x\n", ident); + fprintf(stderr, "llvmgcda: function id=0x%08x\n", ident); #endif if (!output_file) return; @@ -197,18 +223,51 @@ void llvm_gcda_emit_function(uint32_t ident, const char *function_name) { void llvm_gcda_emit_arcs(uint32_t num_counters, uint64_t *counters) { uint32_t i; + uint64_t *old_ctrs = NULL; + uint32_t val = 0; + long pos = 0; - /* Counter #1 (arcs) tag */ if (!output_file) return; + + pos = ftell(output_file); + val = read_int32(); + + if (val != (uint32_t)-1) { + /* There are counters present in the file. Merge them. */ + uint32_t j; + + if (val != 0x01a10000) { + fprintf(stderr, "profiling: invalid magic number (0x%08x)\n", val); + return; + } + + val = read_int32(); + if (val == (uint32_t)-1 || val / 2 != num_counters) { + fprintf(stderr, "profiling: invalid number of counters (%d)\n", val); + return; + } + + old_ctrs = malloc(sizeof(uint64_t) * num_counters); + + for (j = 0; j < num_counters; ++j) + old_ctrs[j] = read_int64(); + } + + /* Reset for writing. */ + fseek(output_file, pos, SEEK_SET); + + /* Counter #1 (arcs) tag */ fwrite("\0\0\xa1\1", 4, 1, output_file); write_int32(num_counters * 2); for (i = 0; i < num_counters; ++i) - write_int64(counters[i]); + write_int64(counters[i] + (old_ctrs ? old_ctrs[i] : 0)); + + free(old_ctrs); #ifdef DEBUG_GCDAPROFILING - printf("llvmgcda: %u arcs\n", num_counters); + fprintf(stderr, "llvmgcda: %u arcs\n", num_counters); for (i = 0; i < num_counters; ++i) - printf("llvmgcda: %llu\n", (unsigned long long)counters[i]); + fprintf(stderr, "llvmgcda: %llu\n", (unsigned long long)counters[i]); #endif } @@ -220,6 +279,6 @@ void llvm_gcda_end_file() { output_file = NULL; #ifdef DEBUG_GCDAPROFILING - printf("llvmgcda: -----\n"); + fprintf(stderr, "llvmgcda: -----\n"); #endif } diff --git a/lib/sanitizer_common/CMakeLists.txt b/lib/sanitizer_common/CMakeLists.txt index d797a56dabd9..ee0e1237c1a9 100644 --- a/lib/sanitizer_common/CMakeLists.txt +++ b/lib/sanitizer_common/CMakeLists.txt @@ -10,26 +10,68 @@ set(SANITIZER_SOURCES sanitizer_mac.cc sanitizer_posix.cc sanitizer_printf.cc + sanitizer_stackdepot.cc + sanitizer_stacktrace.cc sanitizer_symbolizer.cc + sanitizer_symbolizer_itanium.cc + sanitizer_symbolizer_linux.cc + sanitizer_symbolizer_mac.cc + sanitizer_symbolizer_win.cc sanitizer_win.cc ) -set(SANITIZER_CFLAGS "-fPIC -fno-exceptions -funwind-tables -fvisibility=hidden") +# Explicitly list all sanitizer_common headers. Not all of these are +# included in sanitizer_common source files, but we need to depend on +# headers when building our custom unit tests. +set(SANITIZER_HEADERS + sanitizer_allocator.h + sanitizer_atomic_clang.h + sanitizer_atomic_msvc.h + sanitizer_atomic.h + sanitizer_common.h + sanitizer_common_interceptors.inc + sanitizer_common_interceptors_scanf.inc + sanitizer_flags.h + sanitizer_internal_defs.h + sanitizer_lfstack.h + sanitizer_libc.h + sanitizer_list.h + sanitizer_mutex.h + sanitizer_placement_new.h + sanitizer_platform_interceptors.h + sanitizer_procmaps.h + sanitizer_quarantine.h + sanitizer_report_decorator.h + sanitizer_stackdepot.h + sanitizer_stacktrace.h + sanitizer_symbolizer.h + ) -set(SANITIZER_COMMON_DEFINITIONS - SANITIZER_HAS_EXCEPTIONS=1) +set(SANITIZER_CFLAGS ${SANITIZER_COMMON_CFLAGS}) -if(CAN_TARGET_X86_64) - add_library(RTSanitizerCommon.x86_64 OBJECT ${SANITIZER_SOURCES}) - set_property(TARGET RTSanitizerCommon.x86_64 PROPERTY COMPILE_FLAGS - "${SANITIZER_CFLAGS} ${TARGET_X86_64_CFLAGS}") - set_property(TARGET RTSanitizerCommon.x86_64 APPEND PROPERTY COMPILE_DEFINITIONS - ${SANITIZER_COMMON_DEFINITIONS}) +set(SANITIZER_RUNTIME_LIBRARIES) +if(APPLE) + # Build universal binary on APPLE. + add_library(RTSanitizerCommon.osx OBJECT ${SANITIZER_SOURCES}) + set_target_compile_flags(RTSanitizerCommon.osx ${SANITIZER_CFLAGS}) + set_target_properties(RTSanitizerCommon.osx PROPERTIES + OSX_ARCHITECTURES "${SANITIZER_COMMON_SUPPORTED_ARCH}") + list(APPEND SANITIZER_RUNTIME_LIBRARIES RTSanitizerCommon.osx) +elseif(ANDROID) + add_library(RTSanitizerCommon.arm.android OBJECT ${SANITIZER_SOURCES}) + set_target_compile_flags(RTSanitizerCommon.arm.android + ${SANITIZER_CFLAGS}) + list(APPEND SANITIZER_RUNTIME_LIBRARIES RTSanitizerCommon.arm.android) +else() + # Otherwise, build separate libraries for each target. + foreach(arch ${SANITIZER_COMMON_SUPPORTED_ARCH}) + add_compiler_rt_object_library(RTSanitizerCommon ${arch} + SOURCES ${SANITIZER_SOURCES} CFLAGS ${SANITIZER_CFLAGS}) + list(APPEND SANITIZER_RUNTIME_LIBRARIES RTSanitizerCommon.${arch}) + endforeach() endif() -if(CAN_TARGET_I386) - add_library(RTSanitizerCommon.i386 OBJECT ${SANITIZER_SOURCES}) - set_property(TARGET RTSanitizerCommon.i386 PROPERTY COMPILE_FLAGS - "${SANITIZER_CFLAGS} ${TARGET_I386_CFLAGS}") - set_property(TARGET RTSanitizerCommon.i386 APPEND PROPERTY COMPILE_DEFINITIONS - ${SANITIZER_COMMON_DEFINITIONS}) + +# Unit tests for common sanitizer runtime. +if(LLVM_INCLUDE_TESTS) + add_subdirectory(tests) endif() diff --git a/lib/sanitizer_common/sanitizer_allocator.cc b/lib/sanitizer_common/sanitizer_allocator.cc index 816fddf1c5ad..b13a7c6c14c0 100644 --- a/lib/sanitizer_common/sanitizer_allocator.cc +++ b/lib/sanitizer_common/sanitizer_allocator.cc @@ -15,16 +15,16 @@ // FIXME: We should probably use more low-level allocator that would // mmap some pages and split them into chunks to fulfill requests. -#ifdef __linux__ +#if defined(__linux__) && !defined(__ANDROID__) extern "C" void *__libc_malloc(__sanitizer::uptr size); extern "C" void __libc_free(void *ptr); # define LIBC_MALLOC __libc_malloc # define LIBC_FREE __libc_free -#else // __linux__ +#else // __linux__ && !ANDROID # include <stdlib.h> # define LIBC_MALLOC malloc # define LIBC_FREE free -#endif // __linux__ +#endif // __linux__ && !ANDROID namespace __sanitizer { @@ -49,11 +49,30 @@ void InternalFree(void *addr) { LIBC_FREE(addr); } -void *InternalAllocBlock(void *p) { - CHECK_NE(p, (void*)0); - u64 *pp = (u64*)((uptr)p & ~0x7); - for (; pp[0] != kBlockMagic; pp--) {} - return pp + 1; +// LowLevelAllocator +static LowLevelAllocateCallback low_level_alloc_callback; + +void *LowLevelAllocator::Allocate(uptr size) { + // Align allocation size. + size = RoundUpTo(size, 8); + if (allocated_end_ - allocated_current_ < (sptr)size) { + uptr size_to_allocate = Max(size, GetPageSizeCached()); + allocated_current_ = + (char*)MmapOrDie(size_to_allocate, __FUNCTION__); + allocated_end_ = allocated_current_ + size_to_allocate; + if (low_level_alloc_callback) { + low_level_alloc_callback((uptr)allocated_current_, + size_to_allocate); + } + } + CHECK(allocated_end_ - allocated_current_ >= (sptr)size); + void *res = allocated_current_; + allocated_current_ += size; + return res; +} + +void SetLowLevelAllocateCallback(LowLevelAllocateCallback callback) { + low_level_alloc_callback = callback; } } // namespace __sanitizer diff --git a/lib/sanitizer_common/sanitizer_allocator.h b/lib/sanitizer_common/sanitizer_allocator.h new file mode 100644 index 000000000000..ad89c3c870dc --- /dev/null +++ b/lib/sanitizer_common/sanitizer_allocator.h @@ -0,0 +1,989 @@ +//===-- sanitizer_allocator.h -----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Specialized memory allocator for ThreadSanitizer, MemorySanitizer, etc. +// +//===----------------------------------------------------------------------===// + +#ifndef SANITIZER_ALLOCATOR_H +#define SANITIZER_ALLOCATOR_H + +#include "sanitizer_internal_defs.h" +#include "sanitizer_common.h" +#include "sanitizer_libc.h" +#include "sanitizer_list.h" +#include "sanitizer_mutex.h" +#include "sanitizer_lfstack.h" + +namespace __sanitizer { + +// SizeClassMap maps allocation sizes into size classes and back. +// Class 0 corresponds to size 0. +// Classes 1 - 16 correspond to sizes 8 - 128 (size = class_id * 8). +// Next 8 classes: 128 + i * 16 (i = 1 to 8). +// Next 8 classes: 256 + i * 32 (i = 1 to 8). +// ... +// Next 8 classes: 2^k + i * 2^(k-3) (i = 1 to 8). +// Last class corresponds to kMaxSize = 1 << kMaxSizeLog. +// +// This structure of the size class map gives us: +// - Efficient table-free class-to-size and size-to-class functions. +// - Difference between two consequent size classes is betweed 12% and 6% +// +// This class also gives a hint to a thread-caching allocator about the amount +// of chunks that need to be cached per-thread: +// - kMaxNumCached is the maximal number of chunks per size class. +// - (1 << kMaxBytesCachedLog) is the maximal number of bytes per size class. +// +// Part of output of SizeClassMap::Print(): +// c00 => s: 0 diff: +0 00% l 0 cached: 0 0; id 0 +// c01 => s: 8 diff: +8 00% l 3 cached: 256 2048; id 1 +// c02 => s: 16 diff: +8 100% l 4 cached: 256 4096; id 2 +// ... +// c07 => s: 56 diff: +8 16% l 5 cached: 256 14336; id 7 +// +// c08 => s: 64 diff: +8 14% l 6 cached: 256 16384; id 8 +// ... +// c15 => s: 120 diff: +8 07% l 6 cached: 256 30720; id 15 +// +// c16 => s: 128 diff: +8 06% l 7 cached: 256 32768; id 16 +// c17 => s: 144 diff: +16 12% l 7 cached: 227 32688; id 17 +// ... +// c23 => s: 240 diff: +16 07% l 7 cached: 136 32640; id 23 +// +// c24 => s: 256 diff: +16 06% l 8 cached: 128 32768; id 24 +// c25 => s: 288 diff: +32 12% l 8 cached: 113 32544; id 25 +// ... +// c31 => s: 480 diff: +32 07% l 8 cached: 68 32640; id 31 +// +// c32 => s: 512 diff: +32 06% l 9 cached: 64 32768; id 32 + + +template <uptr kMaxSizeLog, uptr kMaxNumCachedT, uptr kMaxBytesCachedLog, + uptr kMinBatchClassT> +class SizeClassMap { + static const uptr kMinSizeLog = 3; + static const uptr kMidSizeLog = kMinSizeLog + 4; + static const uptr kMinSize = 1 << kMinSizeLog; + static const uptr kMidSize = 1 << kMidSizeLog; + static const uptr kMidClass = kMidSize / kMinSize; + static const uptr S = 3; + static const uptr M = (1 << S) - 1; + + public: + static const uptr kMaxNumCached = kMaxNumCachedT; + struct TransferBatch { + TransferBatch *next; + uptr count; + void *batch[kMaxNumCached]; + }; + + static const uptr kMinBatchClass = kMinBatchClassT; + static const uptr kMaxSize = 1 << kMaxSizeLog; + static const uptr kNumClasses = + kMidClass + ((kMaxSizeLog - kMidSizeLog) << S) + 1; + COMPILER_CHECK(kNumClasses >= 32 && kNumClasses <= 256); + static const uptr kNumClassesRounded = + kNumClasses == 32 ? 32 : + kNumClasses <= 64 ? 64 : + kNumClasses <= 128 ? 128 : 256; + + static uptr Size(uptr class_id) { + if (class_id <= kMidClass) + return kMinSize * class_id; + class_id -= kMidClass; + uptr t = kMidSize << (class_id >> S); + return t + (t >> S) * (class_id & M); + } + + static uptr ClassID(uptr size) { + if (size <= kMidSize) + return (size + kMinSize - 1) >> kMinSizeLog; + if (size > kMaxSize) return 0; + uptr l = SANITIZER_WORDSIZE - 1 - __builtin_clzl(size); + uptr hbits = (size >> (l - S)) & M; + uptr lbits = size & ((1 << (l - S)) - 1); + uptr l1 = l - kMidSizeLog; + return kMidClass + (l1 << S) + hbits + (lbits > 0); + } + + static uptr MaxCached(uptr class_id) { + if (class_id == 0) return 0; + uptr n = (1UL << kMaxBytesCachedLog) / Size(class_id); + return Max(1UL, Min(kMaxNumCached, n)); + } + + static void Print() { + uptr prev_s = 0; + uptr total_cached = 0; + for (uptr i = 0; i < kNumClasses; i++) { + uptr s = Size(i); + if (s >= kMidSize / 2 && (s & (s - 1)) == 0) + Printf("\n"); + uptr d = s - prev_s; + uptr p = prev_s ? (d * 100 / prev_s) : 0; + uptr l = SANITIZER_WORDSIZE - 1 - __builtin_clzl(s); + uptr cached = MaxCached(i) * s; + Printf("c%02zd => s: %zd diff: +%zd %02zd%% l %zd " + "cached: %zd %zd; id %zd\n", + i, Size(i), d, p, l, MaxCached(i), cached, ClassID(s)); + total_cached += cached; + prev_s = s; + } + Printf("Total cached: %zd\n", total_cached); + } + + static void Validate() { + for (uptr c = 1; c < kNumClasses; c++) { + // Printf("Validate: c%zd\n", c); + uptr s = Size(c); + CHECK_EQ(ClassID(s), c); + if (c != kNumClasses - 1) + CHECK_EQ(ClassID(s + 1), c + 1); + CHECK_EQ(ClassID(s - 1), c); + if (c) + CHECK_GT(Size(c), Size(c-1)); + } + CHECK_EQ(ClassID(kMaxSize + 1), 0); + + for (uptr s = 1; s <= kMaxSize; s++) { + uptr c = ClassID(s); + // Printf("s%zd => c%zd\n", s, c); + CHECK_LT(c, kNumClasses); + CHECK_GE(Size(c), s); + if (c > 0) + CHECK_LT(Size(c-1), s); + } + + // TransferBatch for kMinBatchClass must fit into the block itself. + const uptr batch_size = sizeof(TransferBatch) + - sizeof(void*) // NOLINT + * (kMaxNumCached - MaxCached(kMinBatchClass)); + CHECK_LE(batch_size, Size(kMinBatchClass)); + // TransferBatch for kMinBatchClass-1 must not fit into the block itself. + const uptr batch_size1 = sizeof(TransferBatch) + - sizeof(void*) // NOLINT + * (kMaxNumCached - MaxCached(kMinBatchClass - 1)); + CHECK_GT(batch_size1, Size(kMinBatchClass - 1)); + } +}; + +typedef SizeClassMap<17, 256, 16, FIRST_32_SECOND_64(33, 36)> + DefaultSizeClassMap; +typedef SizeClassMap<17, 64, 14, FIRST_32_SECOND_64(25, 28)> + CompactSizeClassMap; +template<class SizeClassAllocator> struct SizeClassAllocatorLocalCache; + +// Allocators call these callbacks on mmap/munmap. +struct NoOpMapUnmapCallback { + void OnMap(uptr p, uptr size) const { } + void OnUnmap(uptr p, uptr size) const { } +}; + +// SizeClassAllocator64 -- allocator for 64-bit address space. +// +// Space: a portion of address space of kSpaceSize bytes starting at +// a fixed address (kSpaceBeg). Both constants are powers of two and +// kSpaceBeg is kSpaceSize-aligned. +// At the beginning the entire space is mprotect-ed, then small parts of it +// are mapped on demand. +// +// Region: a part of Space dedicated to a single size class. +// There are kNumClasses Regions of equal size. +// +// UserChunk: a piece of memory returned to user. +// MetaChunk: kMetadataSize bytes of metadata associated with a UserChunk. +// +// A Region looks like this: +// UserChunk1 ... UserChunkN <gap> MetaChunkN ... MetaChunk1 +template <const uptr kSpaceBeg, const uptr kSpaceSize, + const uptr kMetadataSize, class SizeClassMap, + class MapUnmapCallback = NoOpMapUnmapCallback> +class SizeClassAllocator64 { + public: + typedef typename SizeClassMap::TransferBatch Batch; + typedef SizeClassAllocator64<kSpaceBeg, kSpaceSize, kMetadataSize, + SizeClassMap, MapUnmapCallback> ThisT; + typedef SizeClassAllocatorLocalCache<ThisT> AllocatorCache; + + void Init() { + CHECK_EQ(kSpaceBeg, + reinterpret_cast<uptr>(Mprotect(kSpaceBeg, kSpaceSize))); + MapWithCallback(kSpaceEnd, AdditionalSize()); + } + + void MapWithCallback(uptr beg, uptr size) { + CHECK_EQ(beg, reinterpret_cast<uptr>(MmapFixedOrDie(beg, size))); + MapUnmapCallback().OnMap(beg, size); + } + + void UnmapWithCallback(uptr beg, uptr size) { + MapUnmapCallback().OnUnmap(beg, size); + UnmapOrDie(reinterpret_cast<void *>(beg), size); + } + + static bool CanAllocate(uptr size, uptr alignment) { + return size <= SizeClassMap::kMaxSize && + alignment <= SizeClassMap::kMaxSize; + } + + Batch *NOINLINE AllocateBatch(AllocatorCache *c, uptr class_id) { + CHECK_LT(class_id, kNumClasses); + RegionInfo *region = GetRegionInfo(class_id); + Batch *b = region->free_list.Pop(); + if (b == 0) + b = PopulateFreeList(c, class_id, region); + region->n_allocated += b->count; + return b; + } + + void NOINLINE DeallocateBatch(uptr class_id, Batch *b) { + RegionInfo *region = GetRegionInfo(class_id); + region->free_list.Push(b); + region->n_freed += b->count; + } + + static bool PointerIsMine(void *p) { + return reinterpret_cast<uptr>(p) / kSpaceSize == kSpaceBeg / kSpaceSize; + } + + static uptr GetSizeClass(void *p) { + return (reinterpret_cast<uptr>(p) / kRegionSize) % kNumClassesRounded; + } + + void *GetBlockBegin(void *p) { + uptr class_id = GetSizeClass(p); + uptr size = SizeClassMap::Size(class_id); + uptr chunk_idx = GetChunkIdx((uptr)p, size); + uptr reg_beg = (uptr)p & ~(kRegionSize - 1); + uptr beg = chunk_idx * size; + uptr next_beg = beg + size; + RegionInfo *region = GetRegionInfo(class_id); + if (region->mapped_user >= next_beg) + return reinterpret_cast<void*>(reg_beg + beg); + return 0; + } + + static uptr GetActuallyAllocatedSize(void *p) { + CHECK(PointerIsMine(p)); + return SizeClassMap::Size(GetSizeClass(p)); + } + + uptr ClassID(uptr size) { return SizeClassMap::ClassID(size); } + + void *GetMetaData(void *p) { + uptr class_id = GetSizeClass(p); + uptr size = SizeClassMap::Size(class_id); + uptr chunk_idx = GetChunkIdx(reinterpret_cast<uptr>(p), size); + return reinterpret_cast<void*>(kSpaceBeg + (kRegionSize * (class_id + 1)) - + (1 + chunk_idx) * kMetadataSize); + } + + uptr TotalMemoryUsed() { + uptr res = 0; + for (uptr i = 0; i < kNumClasses; i++) + res += GetRegionInfo(i)->allocated_user; + return res; + } + + // Test-only. + void TestOnlyUnmap() { + UnmapWithCallback(kSpaceBeg, kSpaceSize + AdditionalSize()); + } + + void PrintStats() { + uptr total_mapped = 0; + uptr n_allocated = 0; + uptr n_freed = 0; + for (uptr class_id = 1; class_id < kNumClasses; class_id++) { + RegionInfo *region = GetRegionInfo(class_id); + total_mapped += region->mapped_user; + n_allocated += region->n_allocated; + n_freed += region->n_freed; + } + Printf("Stats: SizeClassAllocator64: %zdM mapped in %zd allocations; " + "remains %zd\n", + total_mapped >> 20, n_allocated, n_allocated - n_freed); + for (uptr class_id = 1; class_id < kNumClasses; class_id++) { + RegionInfo *region = GetRegionInfo(class_id); + if (region->mapped_user == 0) continue; + Printf(" %02zd (%zd): total: %zd K allocs: %zd remains: %zd\n", + class_id, + SizeClassMap::Size(class_id), + region->mapped_user >> 10, + region->n_allocated, + region->n_allocated - region->n_freed); + } + } + + typedef SizeClassMap SizeClassMapT; + static const uptr kNumClasses = SizeClassMap::kNumClasses; + static const uptr kNumClassesRounded = SizeClassMap::kNumClassesRounded; + + private: + static const uptr kRegionSize = kSpaceSize / kNumClassesRounded; + static const uptr kSpaceEnd = kSpaceBeg + kSpaceSize; + COMPILER_CHECK(kSpaceBeg % kSpaceSize == 0); + // kRegionSize must be >= 2^32. + COMPILER_CHECK((kRegionSize) >= (1ULL << (SANITIZER_WORDSIZE / 2))); + // Populate the free list with at most this number of bytes at once + // or with one element if its size is greater. + static const uptr kPopulateSize = 1 << 14; + // Call mmap for user memory with at least this size. + static const uptr kUserMapSize = 1 << 15; + // Call mmap for metadata memory with at least this size. + static const uptr kMetaMapSize = 1 << 16; + + struct RegionInfo { + BlockingMutex mutex; + LFStack<Batch> free_list; + uptr allocated_user; // Bytes allocated for user memory. + uptr allocated_meta; // Bytes allocated for metadata. + uptr mapped_user; // Bytes mapped for user memory. + uptr mapped_meta; // Bytes mapped for metadata. + uptr n_allocated, n_freed; // Just stats. + }; + COMPILER_CHECK(sizeof(RegionInfo) >= kCacheLineSize); + + static uptr AdditionalSize() { + return RoundUpTo(sizeof(RegionInfo) * kNumClassesRounded, + GetPageSizeCached()); + } + + RegionInfo *GetRegionInfo(uptr class_id) { + CHECK_LT(class_id, kNumClasses); + RegionInfo *regions = reinterpret_cast<RegionInfo*>(kSpaceBeg + kSpaceSize); + return ®ions[class_id]; + } + + static uptr GetChunkIdx(uptr chunk, uptr size) { + u32 offset = chunk % kRegionSize; + // Here we divide by a non-constant. This is costly. + // We require that kRegionSize is at least 2^32 so that offset is 32-bit. + // We save 2x by using 32-bit div, but may need to use a 256-way switch. + return offset / (u32)size; + } + + Batch *NOINLINE PopulateFreeList(AllocatorCache *c, uptr class_id, + RegionInfo *region) { + BlockingMutexLock l(®ion->mutex); + Batch *b = region->free_list.Pop(); + if (b) + return b; + uptr size = SizeClassMap::Size(class_id); + uptr count = size < kPopulateSize ? SizeClassMap::MaxCached(class_id) : 1; + uptr beg_idx = region->allocated_user; + uptr end_idx = beg_idx + count * size; + uptr region_beg = kSpaceBeg + kRegionSize * class_id; + if (end_idx + size > region->mapped_user) { + // Do the mmap for the user memory. + uptr map_size = kUserMapSize; + while (end_idx + size > region->mapped_user + map_size) + map_size += kUserMapSize; + CHECK_GE(region->mapped_user + map_size, end_idx); + MapWithCallback(region_beg + region->mapped_user, map_size); + region->mapped_user += map_size; + } + uptr total_count = (region->mapped_user - beg_idx - size) + / size / count * count; + region->allocated_meta += total_count * kMetadataSize; + if (region->allocated_meta > region->mapped_meta) { + uptr map_size = kMetaMapSize; + while (region->allocated_meta > region->mapped_meta + map_size) + map_size += kMetaMapSize; + // Do the mmap for the metadata. + CHECK_GE(region->mapped_meta + map_size, region->allocated_meta); + MapWithCallback(region_beg + kRegionSize - + region->mapped_meta - map_size, map_size); + region->mapped_meta += map_size; + } + CHECK_LE(region->allocated_meta, region->mapped_meta); + if (region->allocated_user + region->allocated_meta > kRegionSize) { + Printf("Out of memory. Dying.\n"); + Printf("The process has exhausted %zuMB for size class %zu.\n", + kRegionSize / 1024 / 1024, size); + Die(); + } + for (;;) { + if (class_id < SizeClassMap::kMinBatchClass) + b = (Batch*)c->Allocate(this, SizeClassMap::ClassID(sizeof(Batch))); + else + b = (Batch*)(region_beg + beg_idx); + b->count = count; + for (uptr i = 0; i < count; i++) + b->batch[i] = (void*)(region_beg + beg_idx + i * size); + region->allocated_user += count * size; + CHECK_LE(region->allocated_user, region->mapped_user); + beg_idx += count * size; + if (beg_idx + count * size + size > region->mapped_user) + break; + region->free_list.Push(b); + } + return b; + } +}; + +// SizeClassAllocator32 -- allocator for 32-bit address space. +// This allocator can theoretically be used on 64-bit arch, but there it is less +// efficient than SizeClassAllocator64. +// +// [kSpaceBeg, kSpaceBeg + kSpaceSize) is the range of addresses which can +// be returned by MmapOrDie(). +// +// Region: +// a result of a single call to MmapAlignedOrDie(kRegionSize, kRegionSize). +// Since the regions are aligned by kRegionSize, there are exactly +// kNumPossibleRegions possible regions in the address space and so we keep +// an u8 array possible_regions[kNumPossibleRegions] to store the size classes. +// 0 size class means the region is not used by the allocator. +// +// One Region is used to allocate chunks of a single size class. +// A Region looks like this: +// UserChunk1 .. UserChunkN <gap> MetaChunkN .. MetaChunk1 +// +// In order to avoid false sharing the objects of this class should be +// chache-line aligned. +template <const uptr kSpaceBeg, const u64 kSpaceSize, + const uptr kMetadataSize, class SizeClassMap, + class MapUnmapCallback = NoOpMapUnmapCallback> +class SizeClassAllocator32 { + public: + typedef typename SizeClassMap::TransferBatch Batch; + typedef SizeClassAllocator32<kSpaceBeg, kSpaceSize, kMetadataSize, + SizeClassMap, MapUnmapCallback> ThisT; + typedef SizeClassAllocatorLocalCache<ThisT> AllocatorCache; + + void Init() { + state_ = reinterpret_cast<State *>(MapWithCallback(sizeof(State))); + } + + void *MapWithCallback(uptr size) { + size = RoundUpTo(size, GetPageSizeCached()); + void *res = MmapOrDie(size, "SizeClassAllocator32"); + MapUnmapCallback().OnMap((uptr)res, size); + return res; + } + void UnmapWithCallback(uptr beg, uptr size) { + MapUnmapCallback().OnUnmap(beg, size); + UnmapOrDie(reinterpret_cast<void *>(beg), size); + } + + static bool CanAllocate(uptr size, uptr alignment) { + return size <= SizeClassMap::kMaxSize && + alignment <= SizeClassMap::kMaxSize; + } + + void *GetMetaData(void *p) { + CHECK(PointerIsMine(p)); + uptr mem = reinterpret_cast<uptr>(p); + uptr beg = ComputeRegionBeg(mem); + uptr size = SizeClassMap::Size(GetSizeClass(p)); + u32 offset = mem - beg; + uptr n = offset / (u32)size; // 32-bit division + uptr meta = (beg + kRegionSize) - (n + 1) * kMetadataSize; + return reinterpret_cast<void*>(meta); + } + + Batch *NOINLINE AllocateBatch(AllocatorCache *c, uptr class_id) { + CHECK_LT(class_id, kNumClasses); + SizeClassInfo *sci = GetSizeClassInfo(class_id); + SpinMutexLock l(&sci->mutex); + if (sci->free_list.empty()) + PopulateFreeList(c, sci, class_id); + CHECK(!sci->free_list.empty()); + Batch *b = sci->free_list.front(); + sci->free_list.pop_front(); + return b; + } + + void NOINLINE DeallocateBatch(uptr class_id, Batch *b) { + CHECK_LT(class_id, kNumClasses); + SizeClassInfo *sci = GetSizeClassInfo(class_id); + SpinMutexLock l(&sci->mutex); + sci->free_list.push_front(b); + } + + bool PointerIsMine(void *p) { + return GetSizeClass(p) != 0; + } + + uptr GetSizeClass(void *p) { + return state_->possible_regions[ComputeRegionId(reinterpret_cast<uptr>(p))]; + } + + void *GetBlockBegin(void *p) { + CHECK(PointerIsMine(p)); + uptr mem = reinterpret_cast<uptr>(p); + uptr beg = ComputeRegionBeg(mem); + uptr size = SizeClassMap::Size(GetSizeClass(p)); + u32 offset = mem - beg; + u32 n = offset / (u32)size; // 32-bit division + uptr res = beg + (n * (u32)size); + return reinterpret_cast<void*>(res); + } + + uptr GetActuallyAllocatedSize(void *p) { + CHECK(PointerIsMine(p)); + return SizeClassMap::Size(GetSizeClass(p)); + } + + uptr ClassID(uptr size) { return SizeClassMap::ClassID(size); } + + uptr TotalMemoryUsed() { + // No need to lock here. + uptr res = 0; + for (uptr i = 0; i < kNumPossibleRegions; i++) + if (state_->possible_regions[i]) + res += kRegionSize; + return res; + } + + void TestOnlyUnmap() { + for (uptr i = 0; i < kNumPossibleRegions; i++) + if (state_->possible_regions[i]) + UnmapWithCallback((i * kRegionSize), kRegionSize); + UnmapWithCallback(reinterpret_cast<uptr>(state_), sizeof(State)); + } + + void PrintStats() { + } + + typedef SizeClassMap SizeClassMapT; + static const uptr kNumClasses = SizeClassMap::kNumClasses; + + private: + static const uptr kRegionSizeLog = SANITIZER_WORDSIZE == 64 ? 24 : 20; + static const uptr kRegionSize = 1 << kRegionSizeLog; + static const uptr kNumPossibleRegions = kSpaceSize / kRegionSize; + + struct SizeClassInfo { + SpinMutex mutex; + IntrusiveList<Batch> free_list; + char padding[kCacheLineSize - sizeof(uptr) - sizeof(IntrusiveList<Batch>)]; + }; + COMPILER_CHECK(sizeof(SizeClassInfo) == kCacheLineSize); + + uptr ComputeRegionId(uptr mem) { + uptr res = mem >> kRegionSizeLog; + CHECK_LT(res, kNumPossibleRegions); + return res; + } + + uptr ComputeRegionBeg(uptr mem) { + return mem & ~(kRegionSize - 1); + } + + uptr AllocateRegion(uptr class_id) { + CHECK_LT(class_id, kNumClasses); + uptr res = reinterpret_cast<uptr>(MmapAlignedOrDie(kRegionSize, kRegionSize, + "SizeClassAllocator32")); + MapUnmapCallback().OnMap(res, kRegionSize); + CHECK_EQ(0U, (res & (kRegionSize - 1))); + CHECK_EQ(0U, state_->possible_regions[ComputeRegionId(res)]); + state_->possible_regions[ComputeRegionId(res)] = class_id; + return res; + } + + SizeClassInfo *GetSizeClassInfo(uptr class_id) { + CHECK_LT(class_id, kNumClasses); + return &state_->size_class_info_array[class_id]; + } + + void PopulateFreeList(AllocatorCache *c, SizeClassInfo *sci, uptr class_id) { + uptr size = SizeClassMap::Size(class_id); + uptr reg = AllocateRegion(class_id); + uptr n_chunks = kRegionSize / (size + kMetadataSize); + uptr max_count = SizeClassMap::MaxCached(class_id); + Batch *b = 0; + for (uptr i = reg; i < reg + n_chunks * size; i += size) { + if (b == 0) { + if (class_id < SizeClassMap::kMinBatchClass) + b = (Batch*)c->Allocate(this, SizeClassMap::ClassID(sizeof(Batch))); + else + b = (Batch*)i; + b->count = 0; + } + b->batch[b->count++] = (void*)i; + if (b->count == max_count) { + sci->free_list.push_back(b); + b = 0; + } + } + if (b) + sci->free_list.push_back(b); + } + + struct State { + u8 possible_regions[kNumPossibleRegions]; + SizeClassInfo size_class_info_array[kNumClasses]; + }; + State *state_; +}; + +// Objects of this type should be used as local caches for SizeClassAllocator64 +// or SizeClassAllocator32. Since the typical use of this class is to have one +// object per thread in TLS, is has to be POD. +template<class SizeClassAllocator> +struct SizeClassAllocatorLocalCache { + typedef SizeClassAllocator Allocator; + static const uptr kNumClasses = SizeClassAllocator::kNumClasses; + + // Don't need to call Init if the object is a global (i.e. zero-initialized). + void Init() { + internal_memset(this, 0, sizeof(*this)); + } + + void *Allocate(SizeClassAllocator *allocator, uptr class_id) { + CHECK_NE(class_id, 0UL); + CHECK_LT(class_id, kNumClasses); + PerClass *c = &per_class_[class_id]; + if (UNLIKELY(c->count == 0)) + Refill(allocator, class_id); + void *res = c->batch[--c->count]; + PREFETCH(c->batch[c->count - 1]); + return res; + } + + void Deallocate(SizeClassAllocator *allocator, uptr class_id, void *p) { + CHECK_NE(class_id, 0UL); + CHECK_LT(class_id, kNumClasses); + PerClass *c = &per_class_[class_id]; + if (UNLIKELY(c->count == c->max_count)) + Drain(allocator, class_id); + c->batch[c->count++] = p; + } + + void Drain(SizeClassAllocator *allocator) { + for (uptr class_id = 0; class_id < kNumClasses; class_id++) { + PerClass *c = &per_class_[class_id]; + while (c->count > 0) + Drain(allocator, class_id); + } + } + + // private: + typedef typename SizeClassAllocator::SizeClassMapT SizeClassMap; + typedef typename SizeClassMap::TransferBatch Batch; + struct PerClass { + uptr count; + uptr max_count; + void *batch[2 * SizeClassMap::kMaxNumCached]; + }; + PerClass per_class_[kNumClasses]; + + void InitCache() { + if (per_class_[0].max_count) + return; + for (uptr i = 0; i < kNumClasses; i++) { + PerClass *c = &per_class_[i]; + c->max_count = 2 * SizeClassMap::MaxCached(i); + } + } + + void NOINLINE Refill(SizeClassAllocator *allocator, uptr class_id) { + InitCache(); + PerClass *c = &per_class_[class_id]; + Batch *b = allocator->AllocateBatch(this, class_id); + for (uptr i = 0; i < b->count; i++) + c->batch[i] = b->batch[i]; + c->count = b->count; + if (class_id < SizeClassMap::kMinBatchClass) + Deallocate(allocator, SizeClassMap::ClassID(sizeof(Batch)), b); + } + + void NOINLINE Drain(SizeClassAllocator *allocator, uptr class_id) { + InitCache(); + PerClass *c = &per_class_[class_id]; + Batch *b; + if (class_id < SizeClassMap::kMinBatchClass) + b = (Batch*)Allocate(allocator, SizeClassMap::ClassID(sizeof(Batch))); + else + b = (Batch*)c->batch[0]; + uptr cnt = Min(c->max_count / 2, c->count); + for (uptr i = 0; i < cnt; i++) { + b->batch[i] = c->batch[i]; + c->batch[i] = c->batch[i + c->max_count / 2]; + } + b->count = cnt; + c->count -= cnt; + allocator->DeallocateBatch(class_id, b); + } +}; + +// This class can (de)allocate only large chunks of memory using mmap/unmap. +// The main purpose of this allocator is to cover large and rare allocation +// sizes not covered by more efficient allocators (e.g. SizeClassAllocator64). +template <class MapUnmapCallback = NoOpMapUnmapCallback> +class LargeMmapAllocator { + public: + void Init() { + internal_memset(this, 0, sizeof(*this)); + page_size_ = GetPageSizeCached(); + } + + void *Allocate(uptr size, uptr alignment) { + CHECK(IsPowerOfTwo(alignment)); + uptr map_size = RoundUpMapSize(size); + if (alignment > page_size_) + map_size += alignment; + if (map_size < size) return 0; // Overflow. + uptr map_beg = reinterpret_cast<uptr>( + MmapOrDie(map_size, "LargeMmapAllocator")); + MapUnmapCallback().OnMap(map_beg, map_size); + uptr map_end = map_beg + map_size; + uptr res = map_beg + page_size_; + if (res & (alignment - 1)) // Align. + res += alignment - (res & (alignment - 1)); + CHECK_EQ(0, res & (alignment - 1)); + CHECK_LE(res + size, map_end); + Header *h = GetHeader(res); + h->size = size; + h->map_beg = map_beg; + h->map_size = map_size; + uptr size_log = SANITIZER_WORDSIZE - __builtin_clzl(map_size) - 1; + CHECK_LT(size_log, ARRAY_SIZE(stats.by_size_log)); + { + SpinMutexLock l(&mutex_); + uptr idx = n_chunks_++; + CHECK_LT(idx, kMaxNumChunks); + h->chunk_idx = idx; + chunks_[idx] = h; + stats.n_allocs++; + stats.currently_allocated += map_size; + stats.max_allocated = Max(stats.max_allocated, stats.currently_allocated); + stats.by_size_log[size_log]++; + } + return reinterpret_cast<void*>(res); + } + + void Deallocate(void *p) { + Header *h = GetHeader(p); + { + SpinMutexLock l(&mutex_); + uptr idx = h->chunk_idx; + CHECK_EQ(chunks_[idx], h); + CHECK_LT(idx, n_chunks_); + chunks_[idx] = chunks_[n_chunks_ - 1]; + chunks_[idx]->chunk_idx = idx; + n_chunks_--; + stats.n_frees++; + stats.currently_allocated -= h->map_size; + } + MapUnmapCallback().OnUnmap(h->map_beg, h->map_size); + UnmapOrDie(reinterpret_cast<void*>(h->map_beg), h->map_size); + } + + uptr TotalMemoryUsed() { + SpinMutexLock l(&mutex_); + uptr res = 0; + for (uptr i = 0; i < n_chunks_; i++) { + Header *h = chunks_[i]; + CHECK_EQ(h->chunk_idx, i); + res += RoundUpMapSize(h->size); + } + return res; + } + + bool PointerIsMine(void *p) { + return GetBlockBegin(p) != 0; + } + + uptr GetActuallyAllocatedSize(void *p) { + return RoundUpTo(GetHeader(p)->size, page_size_); + } + + // At least page_size_/2 metadata bytes is available. + void *GetMetaData(void *p) { + // Too slow: CHECK_EQ(p, GetBlockBegin(p)); + CHECK(IsAligned(reinterpret_cast<uptr>(p), page_size_)); + return GetHeader(p) + 1; + } + + void *GetBlockBegin(void *ptr) { + uptr p = reinterpret_cast<uptr>(ptr); + SpinMutexLock l(&mutex_); + uptr nearest_chunk = 0; + // Cache-friendly linear search. + for (uptr i = 0; i < n_chunks_; i++) { + uptr ch = reinterpret_cast<uptr>(chunks_[i]); + if (p < ch) continue; // p is at left to this chunk, skip it. + if (p - ch < p - nearest_chunk) + nearest_chunk = ch; + } + if (!nearest_chunk) + return 0; + Header *h = reinterpret_cast<Header *>(nearest_chunk); + CHECK_GE(nearest_chunk, h->map_beg); + CHECK_LT(nearest_chunk, h->map_beg + h->map_size); + CHECK_LE(nearest_chunk, p); + if (h->map_beg + h->map_size < p) + return 0; + return GetUser(h); + } + + void PrintStats() { + Printf("Stats: LargeMmapAllocator: allocated %zd times, " + "remains %zd (%zd K) max %zd M; by size logs: ", + stats.n_allocs, stats.n_allocs - stats.n_frees, + stats.currently_allocated >> 10, stats.max_allocated >> 20); + for (uptr i = 0; i < ARRAY_SIZE(stats.by_size_log); i++) { + uptr c = stats.by_size_log[i]; + if (!c) continue; + Printf("%zd:%zd; ", i, c); + } + Printf("\n"); + } + + private: + static const int kMaxNumChunks = 1 << FIRST_32_SECOND_64(15, 18); + struct Header { + uptr map_beg; + uptr map_size; + uptr size; + uptr chunk_idx; + }; + + Header *GetHeader(uptr p) { + CHECK_EQ(p % page_size_, 0); + return reinterpret_cast<Header*>(p - page_size_); + } + Header *GetHeader(void *p) { return GetHeader(reinterpret_cast<uptr>(p)); } + + void *GetUser(Header *h) { + CHECK_EQ((uptr)h % page_size_, 0); + return reinterpret_cast<void*>(reinterpret_cast<uptr>(h) + page_size_); + } + + uptr RoundUpMapSize(uptr size) { + return RoundUpTo(size, page_size_) + page_size_; + } + + uptr page_size_; + Header *chunks_[kMaxNumChunks]; + uptr n_chunks_; + struct Stats { + uptr n_allocs, n_frees, currently_allocated, max_allocated, by_size_log[64]; + } stats; + SpinMutex mutex_; +}; + +// This class implements a complete memory allocator by using two +// internal allocators: +// PrimaryAllocator is efficient, but may not allocate some sizes (alignments). +// When allocating 2^x bytes it should return 2^x aligned chunk. +// PrimaryAllocator is used via a local AllocatorCache. +// SecondaryAllocator can allocate anything, but is not efficient. +template <class PrimaryAllocator, class AllocatorCache, + class SecondaryAllocator> // NOLINT +class CombinedAllocator { + public: + void Init() { + primary_.Init(); + secondary_.Init(); + } + + void *Allocate(AllocatorCache *cache, uptr size, uptr alignment, + bool cleared = false) { + // Returning 0 on malloc(0) may break a lot of code. + if (size == 0) + size = 1; + if (size + alignment < size) + return 0; + if (alignment > 8) + size = RoundUpTo(size, alignment); + void *res; + if (primary_.CanAllocate(size, alignment)) + res = cache->Allocate(&primary_, primary_.ClassID(size)); + else + res = secondary_.Allocate(size, alignment); + if (alignment > 8) + CHECK_EQ(reinterpret_cast<uptr>(res) & (alignment - 1), 0); + if (cleared && res) + internal_memset(res, 0, size); + return res; + } + + void Deallocate(AllocatorCache *cache, void *p) { + if (!p) return; + if (primary_.PointerIsMine(p)) + cache->Deallocate(&primary_, primary_.GetSizeClass(p), p); + else + secondary_.Deallocate(p); + } + + void *Reallocate(AllocatorCache *cache, void *p, uptr new_size, + uptr alignment) { + if (!p) + return Allocate(cache, new_size, alignment); + if (!new_size) { + Deallocate(cache, p); + return 0; + } + CHECK(PointerIsMine(p)); + uptr old_size = GetActuallyAllocatedSize(p); + uptr memcpy_size = Min(new_size, old_size); + void *new_p = Allocate(cache, new_size, alignment); + if (new_p) + internal_memcpy(new_p, p, memcpy_size); + Deallocate(cache, p); + return new_p; + } + + bool PointerIsMine(void *p) { + if (primary_.PointerIsMine(p)) + return true; + return secondary_.PointerIsMine(p); + } + + bool FromPrimary(void *p) { + return primary_.PointerIsMine(p); + } + + void *GetMetaData(void *p) { + if (primary_.PointerIsMine(p)) + return primary_.GetMetaData(p); + return secondary_.GetMetaData(p); + } + + void *GetBlockBegin(void *p) { + if (primary_.PointerIsMine(p)) + return primary_.GetBlockBegin(p); + return secondary_.GetBlockBegin(p); + } + + uptr GetActuallyAllocatedSize(void *p) { + if (primary_.PointerIsMine(p)) + return primary_.GetActuallyAllocatedSize(p); + return secondary_.GetActuallyAllocatedSize(p); + } + + uptr TotalMemoryUsed() { + return primary_.TotalMemoryUsed() + secondary_.TotalMemoryUsed(); + } + + void TestOnlyUnmap() { primary_.TestOnlyUnmap(); } + + void SwallowCache(AllocatorCache *cache) { + cache->Drain(&primary_); + } + + void PrintStats() { + primary_.PrintStats(); + secondary_.PrintStats(); + } + + private: + PrimaryAllocator primary_; + SecondaryAllocator secondary_; +}; + +} // namespace __sanitizer + +#endif // SANITIZER_ALLOCATOR_H + diff --git a/lib/sanitizer_common/sanitizer_allocator64.h b/lib/sanitizer_common/sanitizer_allocator64.h deleted file mode 100644 index eb79a128c1cc..000000000000 --- a/lib/sanitizer_common/sanitizer_allocator64.h +++ /dev/null @@ -1,488 +0,0 @@ -//===-- sanitizer_allocator64.h ---------------------------------*- C++ -*-===// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// -// Specialized allocator which works only in 64-bit address space. -// To be used by ThreadSanitizer, MemorySanitizer and possibly other tools. -// The main feature of this allocator is that the header is located far away -// from the user memory region, so that the tool does not use extra shadow -// for the header. -// -// Status: not yet ready. -//===----------------------------------------------------------------------===// -#ifndef SANITIZER_ALLOCATOR_H -#define SANITIZER_ALLOCATOR_H - -#include "sanitizer_common.h" -#include "sanitizer_internal_defs.h" -#include "sanitizer_libc.h" -#include "sanitizer_list.h" -#include "sanitizer_mutex.h" - -namespace __sanitizer { - -// Maps size class id to size and back. -class DefaultSizeClassMap { - private: - // Here we use a spline composed of 5 polynomials of oder 1. - // The first size class is l0, then the classes go with step s0 - // untill they reach l1, after which they go with step s1 and so on. - // Steps should be powers of two for cheap division. - // The size of the last size class should be a power of two. - // There should be at most 256 size classes. - static const uptr l0 = 1 << 4; - static const uptr l1 = 1 << 9; - static const uptr l2 = 1 << 12; - static const uptr l3 = 1 << 15; - static const uptr l4 = 1 << 18; - static const uptr l5 = 1 << 21; - - static const uptr s0 = 1 << 4; - static const uptr s1 = 1 << 6; - static const uptr s2 = 1 << 9; - static const uptr s3 = 1 << 12; - static const uptr s4 = 1 << 15; - - static const uptr u0 = 0 + (l1 - l0) / s0; - static const uptr u1 = u0 + (l2 - l1) / s1; - static const uptr u2 = u1 + (l3 - l2) / s2; - static const uptr u3 = u2 + (l4 - l3) / s3; - static const uptr u4 = u3 + (l5 - l4) / s4; - - public: - static const uptr kNumClasses = u4 + 1; - static const uptr kMaxSize = l5; - static const uptr kMinSize = l0; - - COMPILER_CHECK(kNumClasses <= 256); - COMPILER_CHECK((kMaxSize & (kMaxSize - 1)) == 0); - - static uptr Size(uptr class_id) { - if (class_id <= u0) return l0 + s0 * (class_id - 0); - if (class_id <= u1) return l1 + s1 * (class_id - u0); - if (class_id <= u2) return l2 + s2 * (class_id - u1); - if (class_id <= u3) return l3 + s3 * (class_id - u2); - if (class_id <= u4) return l4 + s4 * (class_id - u3); - return 0; - } - static uptr ClassID(uptr size) { - if (size <= l1) return 0 + (size - l0 + s0 - 1) / s0; - if (size <= l2) return u0 + (size - l1 + s1 - 1) / s1; - if (size <= l3) return u1 + (size - l2 + s2 - 1) / s2; - if (size <= l4) return u2 + (size - l3 + s3 - 1) / s3; - if (size <= l5) return u3 + (size - l4 + s4 - 1) / s4; - return 0; - } -}; - -struct AllocatorListNode { - AllocatorListNode *next; -}; - -typedef IntrusiveList<AllocatorListNode> AllocatorFreeList; - - -// Space: a portion of address space of kSpaceSize bytes starting at -// a fixed address (kSpaceBeg). Both constants are powers of two and -// kSpaceBeg is kSpaceSize-aligned. -// -// Region: a part of Space dedicated to a single size class. -// There are kNumClasses Regions of equal size. -// -// UserChunk: a piece of memory returned to user. -// MetaChunk: kMetadataSize bytes of metadata associated with a UserChunk. -// -// A Region looks like this: -// UserChunk1 ... UserChunkN <gap> MetaChunkN ... MetaChunk1 -template <const uptr kSpaceBeg, const uptr kSpaceSize, - const uptr kMetadataSize, class SizeClassMap> -class SizeClassAllocator64 { - public: - void Init() { - CHECK_EQ(AllocBeg(), reinterpret_cast<uptr>(MmapFixedNoReserve( - AllocBeg(), AllocSize()))); - } - - bool CanAllocate(uptr size, uptr alignment) { - return size <= SizeClassMap::kMaxSize && - alignment <= SizeClassMap::kMaxSize; - } - - void *Allocate(uptr size, uptr alignment) { - CHECK(CanAllocate(size, alignment)); - return AllocateBySizeClass(SizeClassMap::ClassID(size)); - } - - void Deallocate(void *p) { - CHECK(PointerIsMine(p)); - DeallocateBySizeClass(p, GetSizeClass(p)); - } - - // Allocate several chunks of the given class_id. - void BulkAllocate(uptr class_id, AllocatorFreeList *free_list) { - CHECK_LT(class_id, kNumClasses); - RegionInfo *region = GetRegionInfo(class_id); - SpinMutexLock l(®ion->mutex); - if (region->free_list.empty()) { - PopulateFreeList(class_id, region); - } - CHECK(!region->free_list.empty()); - // Just take as many chunks as we have in the free list now. - // FIXME: this might be too much. - free_list->append_front(®ion->free_list); - CHECK(region->free_list.empty()); - } - - // Swallow the entire free_list for the given class_id. - void BulkDeallocate(uptr class_id, AllocatorFreeList *free_list) { - CHECK_LT(class_id, kNumClasses); - RegionInfo *region = GetRegionInfo(class_id); - SpinMutexLock l(®ion->mutex); - region->free_list.append_front(free_list); - } - - bool PointerIsMine(void *p) { - return reinterpret_cast<uptr>(p) / kSpaceSize == kSpaceBeg / kSpaceSize; - } - uptr GetSizeClass(void *p) { - return (reinterpret_cast<uptr>(p) / kRegionSize) % kNumClasses; - } - - uptr GetActuallyAllocatedSize(void *p) { - CHECK(PointerIsMine(p)); - return SizeClassMap::Size(GetSizeClass(p)); - } - - uptr ClassID(uptr size) { return SizeClassMap::ClassID(size); } - - void *GetMetaData(void *p) { - uptr class_id = GetSizeClass(p); - uptr chunk_idx = GetChunkIdx(reinterpret_cast<uptr>(p), class_id); - return reinterpret_cast<void*>(kSpaceBeg + (kRegionSize * (class_id + 1)) - - (1 + chunk_idx) * kMetadataSize); - } - - uptr TotalMemoryUsed() { - uptr res = 0; - for (uptr i = 0; i < kNumClasses; i++) - res += GetRegionInfo(i)->allocated_user; - return res; - } - - // Test-only. - void TestOnlyUnmap() { - UnmapOrDie(reinterpret_cast<void*>(AllocBeg()), AllocSize()); - } - - static const uptr kNumClasses = 256; // Power of two <= 256 - - private: - COMPILER_CHECK(kNumClasses <= SizeClassMap::kNumClasses); - static const uptr kRegionSize = kSpaceSize / kNumClasses; - COMPILER_CHECK((kRegionSize >> 32) > 0); // kRegionSize must be >= 2^32. - // Populate the free list with at most this number of bytes at once - // or with one element if its size is greater. - static const uptr kPopulateSize = 1 << 18; - - struct RegionInfo { - SpinMutex mutex; - AllocatorFreeList free_list; - uptr allocated_user; // Bytes allocated for user memory. - uptr allocated_meta; // Bytes allocated for metadata. - char padding[kCacheLineSize - 3 * sizeof(uptr) - sizeof(AllocatorFreeList)]; - }; - COMPILER_CHECK(sizeof(RegionInfo) == kCacheLineSize); - - uptr AdditionalSize() { - uptr res = sizeof(RegionInfo) * kNumClasses; - CHECK_EQ(res % kPageSize, 0); - return res; - } - uptr AllocBeg() { return kSpaceBeg - AdditionalSize(); } - uptr AllocSize() { return kSpaceSize + AdditionalSize(); } - - RegionInfo *GetRegionInfo(uptr class_id) { - CHECK_LT(class_id, kNumClasses); - RegionInfo *regions = reinterpret_cast<RegionInfo*>(kSpaceBeg); - return ®ions[-1 - class_id]; - } - - uptr GetChunkIdx(uptr chunk, uptr class_id) { - u32 offset = chunk % kRegionSize; - // Here we divide by a non-constant. This is costly. - // We require that kRegionSize is at least 2^32 so that offset is 32-bit. - // We save 2x by using 32-bit div, but may need to use a 256-way switch. - return offset / (u32)SizeClassMap::Size(class_id); - } - - void PopulateFreeList(uptr class_id, RegionInfo *region) { - uptr size = SizeClassMap::Size(class_id); - uptr beg_idx = region->allocated_user; - uptr end_idx = beg_idx + kPopulateSize; - region->free_list.clear(); - uptr region_beg = kSpaceBeg + kRegionSize * class_id; - uptr idx = beg_idx; - uptr i = 0; - do { // do-while loop because we need to put at least one item. - uptr p = region_beg + idx; - region->free_list.push_front(reinterpret_cast<AllocatorListNode*>(p)); - idx += size; - i++; - } while (idx < end_idx); - region->allocated_user += idx - beg_idx; - region->allocated_meta += i * kMetadataSize; - CHECK_LT(region->allocated_user + region->allocated_meta, kRegionSize); - } - - void *AllocateBySizeClass(uptr class_id) { - CHECK_LT(class_id, kNumClasses); - RegionInfo *region = GetRegionInfo(class_id); - SpinMutexLock l(®ion->mutex); - if (region->free_list.empty()) { - PopulateFreeList(class_id, region); - } - CHECK(!region->free_list.empty()); - AllocatorListNode *node = region->free_list.front(); - region->free_list.pop_front(); - return reinterpret_cast<void*>(node); - } - - void DeallocateBySizeClass(void *p, uptr class_id) { - RegionInfo *region = GetRegionInfo(class_id); - SpinMutexLock l(®ion->mutex); - region->free_list.push_front(reinterpret_cast<AllocatorListNode*>(p)); - } -}; - -// Objects of this type should be used as local caches for SizeClassAllocator64. -// Since the typical use of this class is to have one object per thread in TLS, -// is has to be POD. -template<const uptr kNumClasses, class SizeClassAllocator> -struct SizeClassAllocatorLocalCache { - // Don't need to call Init if the object is a global (i.e. zero-initialized). - void Init() { - internal_memset(this, 0, sizeof(*this)); - } - - void *Allocate(SizeClassAllocator *allocator, uptr class_id) { - CHECK_LT(class_id, kNumClasses); - AllocatorFreeList *free_list = &free_lists_[class_id]; - if (free_list->empty()) - allocator->BulkAllocate(class_id, free_list); - CHECK(!free_list->empty()); - void *res = free_list->front(); - free_list->pop_front(); - return res; - } - - void Deallocate(SizeClassAllocator *allocator, uptr class_id, void *p) { - CHECK_LT(class_id, kNumClasses); - free_lists_[class_id].push_front(reinterpret_cast<AllocatorListNode*>(p)); - } - - void Drain(SizeClassAllocator *allocator) { - for (uptr i = 0; i < kNumClasses; i++) { - allocator->BulkDeallocate(i, &free_lists_[i]); - CHECK(free_lists_[i].empty()); - } - } - - // private: - AllocatorFreeList free_lists_[kNumClasses]; -}; - -// This class can (de)allocate only large chunks of memory using mmap/unmap. -// The main purpose of this allocator is to cover large and rare allocation -// sizes not covered by more efficient allocators (e.g. SizeClassAllocator64). -// The result is always page-aligned. -class LargeMmapAllocator { - public: - void Init() { - internal_memset(this, 0, sizeof(*this)); - } - void *Allocate(uptr size, uptr alignment) { - CHECK_LE(alignment, kPageSize); // Not implemented. Do we need it? - uptr map_size = RoundUpMapSize(size); - void *map = MmapOrDie(map_size, "LargeMmapAllocator"); - void *res = reinterpret_cast<void*>(reinterpret_cast<uptr>(map) - + kPageSize); - Header *h = GetHeader(res); - h->size = size; - { - SpinMutexLock l(&mutex_); - h->next = list_; - h->prev = 0; - if (list_) - list_->prev = h; - list_ = h; - } - return res; - } - - void Deallocate(void *p) { - Header *h = GetHeader(p); - uptr map_size = RoundUpMapSize(h->size); - { - SpinMutexLock l(&mutex_); - Header *prev = h->prev; - Header *next = h->next; - if (prev) - prev->next = next; - if (next) - next->prev = prev; - if (h == list_) - list_ = next; - } - UnmapOrDie(h, map_size); - } - - uptr TotalMemoryUsed() { - SpinMutexLock l(&mutex_); - uptr res = 0; - for (Header *l = list_; l; l = l->next) { - res += RoundUpMapSize(l->size); - } - return res; - } - - bool PointerIsMine(void *p) { - // Fast check. - if ((reinterpret_cast<uptr>(p) % kPageSize) != 0) return false; - SpinMutexLock l(&mutex_); - for (Header *l = list_; l; l = l->next) { - if (GetUser(l) == p) return true; - } - return false; - } - - uptr GetActuallyAllocatedSize(void *p) { - return RoundUpMapSize(GetHeader(p)->size) - kPageSize; - } - - // At least kPageSize/2 metadata bytes is available. - void *GetMetaData(void *p) { - return GetHeader(p) + 1; - } - - private: - struct Header { - uptr size; - Header *next; - Header *prev; - }; - - Header *GetHeader(void *p) { - return reinterpret_cast<Header*>(reinterpret_cast<uptr>(p) - kPageSize); - } - - void *GetUser(Header *h) { - return reinterpret_cast<void*>(reinterpret_cast<uptr>(h) + kPageSize); - } - - uptr RoundUpMapSize(uptr size) { - return RoundUpTo(size, kPageSize) + kPageSize; - } - - Header *list_; - SpinMutex mutex_; -}; - -// This class implements a complete memory allocator by using two -// internal allocators: -// PrimaryAllocator is efficient, but may not allocate some sizes (alignments). -// When allocating 2^x bytes it should return 2^x aligned chunk. -// PrimaryAllocator is used via a local AllocatorCache. -// SecondaryAllocator can allocate anything, but is not efficient. -template <class PrimaryAllocator, class AllocatorCache, - class SecondaryAllocator> // NOLINT -class CombinedAllocator { - public: - void Init() { - primary_.Init(); - secondary_.Init(); - } - - void *Allocate(AllocatorCache *cache, uptr size, uptr alignment, - bool cleared = false) { - // Returning 0 on malloc(0) may break a lot of code. - if (size == 0) size = 1; - if (alignment > 8) - size = RoundUpTo(size, alignment); - void *res; - if (primary_.CanAllocate(size, alignment)) - res = cache->Allocate(&primary_, primary_.ClassID(size)); - else - res = secondary_.Allocate(size, alignment); - if (alignment > 8) - CHECK_EQ(reinterpret_cast<uptr>(res) & (alignment - 1), 0); - if (cleared) - internal_memset(res, 0, size); - return res; - } - - void Deallocate(AllocatorCache *cache, void *p) { - if (!p) return; - if (primary_.PointerIsMine(p)) - cache->Deallocate(&primary_, primary_.GetSizeClass(p), p); - else - secondary_.Deallocate(p); - } - - void *Reallocate(AllocatorCache *cache, void *p, uptr new_size, - uptr alignment) { - if (!p) - return Allocate(cache, new_size, alignment); - if (!new_size) { - Deallocate(cache, p); - return 0; - } - CHECK(PointerIsMine(p)); - uptr old_size = GetActuallyAllocatedSize(p); - uptr memcpy_size = Min(new_size, old_size); - void *new_p = Allocate(cache, new_size, alignment); - if (new_p) - internal_memcpy(new_p, p, memcpy_size); - Deallocate(cache, p); - return new_p; - } - - bool PointerIsMine(void *p) { - if (primary_.PointerIsMine(p)) - return true; - return secondary_.PointerIsMine(p); - } - - void *GetMetaData(void *p) { - if (primary_.PointerIsMine(p)) - return primary_.GetMetaData(p); - return secondary_.GetMetaData(p); - } - - uptr GetActuallyAllocatedSize(void *p) { - if (primary_.PointerIsMine(p)) - return primary_.GetActuallyAllocatedSize(p); - return secondary_.GetActuallyAllocatedSize(p); - } - - uptr TotalMemoryUsed() { - return primary_.TotalMemoryUsed() + secondary_.TotalMemoryUsed(); - } - - void TestOnlyUnmap() { primary_.TestOnlyUnmap(); } - - void SwallowCache(AllocatorCache *cache) { - cache->Drain(&primary_); - } - - private: - PrimaryAllocator primary_; - SecondaryAllocator secondary_; -}; - -} // namespace __sanitizer - -#endif // SANITIZER_ALLOCATOR_H diff --git a/lib/sanitizer_common/sanitizer_atomic_clang.h b/lib/sanitizer_common/sanitizer_atomic_clang.h index af7044165a61..7f73df3bd455 100644 --- a/lib/sanitizer_common/sanitizer_atomic_clang.h +++ b/lib/sanitizer_common/sanitizer_atomic_clang.h @@ -41,6 +41,7 @@ INLINE typename T::Type atomic_load( | memory_order_acquire | memory_order_seq_cst)); DCHECK(!((uptr)a % sizeof(*a))); typename T::Type v; + // FIXME(dvyukov): 64-bit load is not atomic on 32-bits. if (mo == memory_order_relaxed) { v = a->val_dont_use; } else { @@ -56,6 +57,7 @@ INLINE void atomic_store(volatile T *a, typename T::Type v, memory_order mo) { DCHECK(mo & (memory_order_relaxed | memory_order_release | memory_order_seq_cst)); DCHECK(!((uptr)a % sizeof(*a))); + // FIXME(dvyukov): 64-bit store is not atomic on 32-bits. if (mo == memory_order_relaxed) { a->val_dont_use = v; } else { diff --git a/lib/sanitizer_common/sanitizer_atomic_msvc.h b/lib/sanitizer_common/sanitizer_atomic_msvc.h index 2a15b59a3442..58a6a20ec9c5 100644 --- a/lib/sanitizer_common/sanitizer_atomic_msvc.h +++ b/lib/sanitizer_common/sanitizer_atomic_msvc.h @@ -25,6 +25,31 @@ extern "C" long _InterlockedExchangeAdd( // NOLINT long volatile * Addend, long Value); // NOLINT #pragma intrinsic(_InterlockedExchangeAdd) +#ifdef _WIN64 +extern "C" void *_InterlockedCompareExchangePointer( + void *volatile *Destination, + void *Exchange, void *Comparand); +#pragma intrinsic(_InterlockedCompareExchangePointer) +#else +// There's no _InterlockedCompareExchangePointer intrinsic on x86, +// so call _InterlockedCompareExchange instead. +extern "C" +long __cdecl _InterlockedCompareExchange( // NOLINT + long volatile *Destination, // NOLINT + long Exchange, long Comparand); // NOLINT +#pragma intrinsic(_InterlockedCompareExchange) + +inline static void *_InterlockedCompareExchangePointer( + void *volatile *Destination, + void *Exchange, void *Comparand) { + return reinterpret_cast<void*>( + _InterlockedCompareExchange( + reinterpret_cast<long volatile*>(Destination), // NOLINT + reinterpret_cast<long>(Exchange), // NOLINT + reinterpret_cast<long>(Comparand))); // NOLINT +} +#endif + namespace __sanitizer { INLINE void atomic_signal_fence(memory_order) { @@ -47,6 +72,7 @@ INLINE typename T::Type atomic_load( | memory_order_acquire | memory_order_seq_cst)); DCHECK(!((uptr)a % sizeof(*a))); typename T::Type v; + // FIXME(dvyukov): 64-bit load is not atomic on 32-bits. if (mo == memory_order_relaxed) { v = a->val_dont_use; } else { @@ -62,6 +88,7 @@ INLINE void atomic_store(volatile T *a, typename T::Type v, memory_order mo) { DCHECK(mo & (memory_order_relaxed | memory_order_release | memory_order_seq_cst)); DCHECK(!((uptr)a % sizeof(*a))); + // FIXME(dvyukov): 64-bit store is not atomic on 32-bits. if (mo == memory_order_relaxed) { a->val_dont_use = v; } else { @@ -107,6 +134,27 @@ INLINE u16 atomic_exchange(volatile atomic_uint16_t *a, return v; } +INLINE bool atomic_compare_exchange_strong(volatile atomic_uintptr_t *a, + uptr *cmp, + uptr xchg, + memory_order mo) { + uptr cmpv = *cmp; + uptr prev = (uptr)_InterlockedCompareExchangePointer( + (void*volatile*)&a->val_dont_use, (void*)xchg, (void*)cmpv); + if (prev == cmpv) + return true; + *cmp = prev; + return false; +} + +template<typename T> +INLINE bool atomic_compare_exchange_weak(volatile T *a, + typename T::Type *cmp, + typename T::Type xchg, + memory_order mo) { + return atomic_compare_exchange_strong(a, cmp, xchg, mo); +} + } // namespace __sanitizer #endif // SANITIZER_ATOMIC_CLANG_H diff --git a/lib/sanitizer_common/sanitizer_common.cc b/lib/sanitizer_common/sanitizer_common.cc index 6dd1ff91726e..4a8d9a749bf8 100644 --- a/lib/sanitizer_common/sanitizer_common.cc +++ b/lib/sanitizer_common/sanitizer_common.cc @@ -16,18 +16,90 @@ namespace __sanitizer { +uptr GetPageSizeCached() { + static uptr PageSize; + if (!PageSize) + PageSize = GetPageSize(); + return PageSize; +} + +static bool log_to_file = false; // Set to true by __sanitizer_set_report_path + +// By default, dump to stderr. If |log_to_file| is true and |report_fd_pid| +// isn't equal to the current PID, try to obtain file descriptor by opening +// file "report_path_prefix.<PID>". +static fd_t report_fd = kStderrFd; +static char report_path_prefix[4096]; // Set via __sanitizer_set_report_path. +// PID of process that opened |report_fd|. If a fork() occurs, the PID of the +// child thread will be different from |report_fd_pid|. +static int report_fd_pid = 0; + +static void (*DieCallback)(void); +void SetDieCallback(void (*callback)(void)) { + DieCallback = callback; +} + +void NORETURN Die() { + if (DieCallback) { + DieCallback(); + } + Exit(1); +} + +static CheckFailedCallbackType CheckFailedCallback; +void SetCheckFailedCallback(CheckFailedCallbackType callback) { + CheckFailedCallback = callback; +} + +void NORETURN CheckFailed(const char *file, int line, const char *cond, + u64 v1, u64 v2) { + if (CheckFailedCallback) { + CheckFailedCallback(file, line, cond, v1, v2); + } + Report("Sanitizer CHECK failed: %s:%d %s (%lld, %lld)\n", file, line, cond, + v1, v2); + Die(); +} + +static void MaybeOpenReportFile() { + if (!log_to_file || (report_fd_pid == GetPid())) return; + char report_path_full[4096]; + internal_snprintf(report_path_full, sizeof(report_path_full), + "%s.%d", report_path_prefix, GetPid()); + fd_t fd = internal_open(report_path_full, true); + if (fd == kInvalidFd) { + report_fd = kStderrFd; + log_to_file = false; + Report("ERROR: Can't open file: %s\n", report_path_full); + Die(); + } + if (report_fd != kInvalidFd) { + // We're in the child. Close the parent's log. + internal_close(report_fd); + } + report_fd = fd; + report_fd_pid = GetPid(); +} + +bool PrintsToTty() { + MaybeOpenReportFile(); + return internal_isatty(report_fd); +} + void RawWrite(const char *buffer) { static const char *kRawWriteError = "RawWrite can't output requested buffer!"; uptr length = (uptr)internal_strlen(buffer); - if (length != internal_write(2, buffer, length)) { - internal_write(2, kRawWriteError, internal_strlen(kRawWriteError)); + MaybeOpenReportFile(); + if (length != internal_write(report_fd, buffer, length)) { + internal_write(report_fd, kRawWriteError, internal_strlen(kRawWriteError)); Die(); } } uptr ReadFileToBuffer(const char *file_name, char **buff, uptr *buff_size, uptr max_len) { - const uptr kMinFileLen = kPageSize; + uptr PageSize = GetPageSizeCached(); + uptr kMinFileLen = PageSize; uptr read_len = 0; *buff = 0; *buff_size = 0; @@ -41,8 +113,8 @@ uptr ReadFileToBuffer(const char *file_name, char **buff, // Read up to one page at a time. read_len = 0; bool reached_eof = false; - while (read_len + kPageSize <= size) { - uptr just_read = internal_read(fd, *buff + read_len, kPageSize); + while (read_len + PageSize <= size) { + uptr just_read = internal_read(fd, *buff + read_len, PageSize); if (just_read == 0) { reached_eof = true; break; @@ -97,4 +169,57 @@ void SortArray(uptr *array, uptr size) { } } +// We want to map a chunk of address space aligned to 'alignment'. +// We do it by maping a bit more and then unmaping redundant pieces. +// We probably can do it with fewer syscalls in some OS-dependent way. +void *MmapAlignedOrDie(uptr size, uptr alignment, const char *mem_type) { +// uptr PageSize = GetPageSizeCached(); + CHECK(IsPowerOfTwo(size)); + CHECK(IsPowerOfTwo(alignment)); + uptr map_size = size + alignment; + uptr map_res = (uptr)MmapOrDie(map_size, mem_type); + uptr map_end = map_res + map_size; + uptr res = map_res; + if (res & (alignment - 1)) // Not aligned. + res = (map_res + alignment) & ~(alignment - 1); + uptr end = res + size; + if (res != map_res) + UnmapOrDie((void*)map_res, res - map_res); + if (end != map_end) + UnmapOrDie((void*)end, map_end - end); + return (void*)res; +} + } // namespace __sanitizer + +using namespace __sanitizer; // NOLINT + +extern "C" { +void __sanitizer_set_report_path(const char *path) { + if (!path) return; + uptr len = internal_strlen(path); + if (len > sizeof(report_path_prefix) - 100) { + Report("ERROR: Path is too long: %c%c%c%c%c%c%c%c...\n", + path[0], path[1], path[2], path[3], + path[4], path[5], path[6], path[7]); + Die(); + } + internal_strncpy(report_path_prefix, path, sizeof(report_path_prefix)); + report_path_prefix[len] = '\0'; + report_fd = kInvalidFd; + log_to_file = true; +} + +void __sanitizer_set_report_fd(int fd) { + if (report_fd != kStdoutFd && + report_fd != kStderrFd && + report_fd != kInvalidFd) + internal_close(report_fd); + report_fd = fd; +} + +void NOINLINE __sanitizer_sandbox_on_notify(void *reserved) { + (void)reserved; + PrepareForSandboxing(); +} +} // extern "C" diff --git a/lib/sanitizer_common/sanitizer_common.h b/lib/sanitizer_common/sanitizer_common.h index 4c7c1e9d86ed..1d002398c785 100644 --- a/lib/sanitizer_common/sanitizer_common.h +++ b/lib/sanitizer_common/sanitizer_common.h @@ -21,19 +21,21 @@ namespace __sanitizer { // Constants. -const uptr kWordSize = __WORDSIZE / 8; +const uptr kWordSize = SANITIZER_WORDSIZE / 8; const uptr kWordSizeInBits = 8 * kWordSize; -const uptr kPageSizeBits = 12; -const uptr kPageSize = 1UL << kPageSizeBits; -const uptr kCacheLineSize = 64; -#ifndef _WIN32 -const uptr kMmapGranularity = kPageSize; + +#if defined(__powerpc__) || defined(__powerpc64__) +const uptr kCacheLineSize = 128; #else -const uptr kMmapGranularity = 1UL << 16; +const uptr kCacheLineSize = 64; #endif +uptr GetPageSize(); +uptr GetPageSizeCached(); +uptr GetMmapGranularity(); // Threads int GetPid(); +uptr GetTid(); uptr GetThreadSelf(); void GetThreadStackTopAndBottom(bool at_initialization, uptr *stack_top, uptr *stack_bottom); @@ -42,21 +44,66 @@ void GetThreadStackTopAndBottom(bool at_initialization, uptr *stack_top, void *MmapOrDie(uptr size, const char *mem_type); void UnmapOrDie(void *addr, uptr size); void *MmapFixedNoReserve(uptr fixed_addr, uptr size); +void *MmapFixedOrDie(uptr fixed_addr, uptr size); void *Mprotect(uptr fixed_addr, uptr size); +// Map aligned chunk of address space; size and alignment are powers of two. +void *MmapAlignedOrDie(uptr size, uptr alignment, const char *mem_type); // Used to check if we can map shadow memory to a fixed location. bool MemoryRangeIsAvailable(uptr range_start, uptr range_end); +void FlushUnneededShadowMemory(uptr addr, uptr size); // Internal allocator void *InternalAlloc(uptr size); void InternalFree(void *p); -// Given the pointer p into a valid allocated block, -// returns a pointer to the beginning of the block. -void *InternalAllocBlock(void *p); + +// InternalScopedBuffer can be used instead of large stack arrays to +// keep frame size low. +// FIXME: use InternalAlloc instead of MmapOrDie once +// InternalAlloc is made libc-free. +template<typename T> +class InternalScopedBuffer { + public: + explicit InternalScopedBuffer(uptr cnt) { + cnt_ = cnt; + ptr_ = (T*)MmapOrDie(cnt * sizeof(T), "InternalScopedBuffer"); + } + ~InternalScopedBuffer() { + UnmapOrDie(ptr_, cnt_ * sizeof(T)); + } + T &operator[](uptr i) { return ptr_[i]; } + T *data() { return ptr_; } + uptr size() { return cnt_ * sizeof(T); } + + private: + T *ptr_; + uptr cnt_; + // Disallow evil constructors. + InternalScopedBuffer(const InternalScopedBuffer&); + void operator=(const InternalScopedBuffer&); +}; + +// Simple low-level (mmap-based) allocator for internal use. Doesn't have +// constructor, so all instances of LowLevelAllocator should be +// linker initialized. +class LowLevelAllocator { + public: + // Requires an external lock. + void *Allocate(uptr size); + private: + char *allocated_end_; + char *allocated_current_; +}; +typedef void (*LowLevelAllocateCallback)(uptr ptr, uptr size); +// Allows to register tool-specific callbacks for LowLevelAllocator. +// Passing NULL removes the callback. +void SetLowLevelAllocateCallback(LowLevelAllocateCallback callback); // IO void RawWrite(const char *buffer); +bool PrintsToTty(); void Printf(const char *format, ...); void Report(const char *format, ...); +void SetPrintfAndReportCallback(void (*callback)(const char *)); // Opens the file 'file_name" and reads up to 'max_len' bytes. // The resulting buffer is mmaped and stored in '*buff'. @@ -69,19 +116,44 @@ uptr ReadFileToBuffer(const char *file_name, char **buff, // in '*buff_size'. void *MapFileToMemory(const char *file_name, uptr *buff_size); +// OS +void DisableCoreDumper(); +void DumpProcessMap(); +bool FileExists(const char *filename); const char *GetEnv(const char *name); const char *GetPwd(); +void ReExec(); +bool StackSizeIsUnlimited(); +void SetStackSizeLimitInBytes(uptr limit); +void PrepareForSandboxing(); // Other -void DisableCoreDumper(); -void DumpProcessMap(); void SleepForSeconds(int seconds); void SleepForMillis(int millis); -void NORETURN Exit(int exitcode); -void NORETURN Abort(); int Atexit(void (*function)(void)); void SortArray(uptr *array, uptr size); +// Exit +void NORETURN Abort(); +void NORETURN Exit(int exitcode); +void NORETURN Die(); +void NORETURN SANITIZER_INTERFACE_ATTRIBUTE +CheckFailed(const char *file, int line, const char *cond, u64 v1, u64 v2); + +// Set the name of the current thread to 'name', return true on succees. +// The name may be truncated to a system-dependent limit. +bool SanitizerSetThreadName(const char *name); +// Get the name of the current thread (no more than max_len bytes), +// return true on succees. name should have space for at least max_len+1 bytes. +bool SanitizerGetThreadName(char *name, int max_len); + +// Specific tools may override behavior of "Die" and "CheckFailed" functions +// to do tool-specific job. +void SetDieCallback(void (*callback)(void)); +typedef void (*CheckFailedCallbackType)(const char *, int, const char *, + u64, u64); +void SetCheckFailedCallback(CheckFailedCallbackType callback); + // Math INLINE bool IsPowerOfTwo(uptr x) { return (x & (x - 1)) == 0; @@ -90,6 +162,12 @@ INLINE uptr RoundUpTo(uptr size, uptr boundary) { CHECK(IsPowerOfTwo(boundary)); return (size + boundary - 1) & ~(boundary - 1); } +INLINE uptr RoundDownTo(uptr x, uptr boundary) { + return x & ~(boundary - 1); +} +INLINE bool IsAligned(uptr a, uptr alignment) { + return (a & (alignment - 1)) == 0; +} // Don't use std::min, std::max or std::swap, to minimize dependency // on libstdc++. template<class T> T Min(T a, T b) { return a < b ? a : b; } @@ -112,7 +190,7 @@ INLINE int ToLower(int c) { return (c >= 'A' && c <= 'Z') ? (c + 'a' - 'A') : c; } -#if __WORDSIZE == 64 +#if SANITIZER_WORDSIZE == 64 # define FIRST_32_SECOND_64(a, b) (b) #else # define FIRST_32_SECOND_64(a, b) (a) diff --git a/lib/sanitizer_common/sanitizer_common_interceptors.inc b/lib/sanitizer_common/sanitizer_common_interceptors.inc new file mode 100644 index 000000000000..8bc2e8b5c292 --- /dev/null +++ b/lib/sanitizer_common/sanitizer_common_interceptors.inc @@ -0,0 +1,224 @@ +//===-- sanitizer_common_interceptors.inc -----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Common function interceptors for tools like AddressSanitizer, +// ThreadSanitizer, MemorySanitizer, etc. +// +// This file should be included into the tool's interceptor file, +// which has to define it's own macros: +// COMMON_INTERCEPTOR_ENTER +// COMMON_INTERCEPTOR_READ_RANGE +// COMMON_INTERCEPTOR_WRITE_RANGE +// COMMON_INTERCEPTOR_FD_ACQUIRE +// COMMON_INTERCEPTOR_FD_RELEASE +// COMMON_INTERCEPTOR_SET_THREAD_NAME +//===----------------------------------------------------------------------===// +#include "interception/interception.h" +#include "sanitizer_platform_interceptors.h" + +#include <stdarg.h> + +#if SANITIZER_INTERCEPT_READ +INTERCEPTOR(SSIZE_T, read, int fd, void *ptr, SIZE_T count) { + void* ctx; + COMMON_INTERCEPTOR_ENTER(ctx, read, fd, ptr, count); + SSIZE_T res = REAL(read)(fd, ptr, count); + if (res > 0) + COMMON_INTERCEPTOR_WRITE_RANGE(ctx, ptr, res); + if (res >= 0 && fd >= 0) + COMMON_INTERCEPTOR_FD_ACQUIRE(ctx, fd); + return res; +} +# define INIT_READ INTERCEPT_FUNCTION(read) +#else +# define INIT_READ +#endif + +#if SANITIZER_INTERCEPT_PREAD +INTERCEPTOR(SSIZE_T, pread, int fd, void *ptr, SIZE_T count, OFF_T offset) { + void* ctx; + COMMON_INTERCEPTOR_ENTER(ctx, pread, fd, ptr, count, offset); + SSIZE_T res = REAL(pread)(fd, ptr, count, offset); + if (res > 0) + COMMON_INTERCEPTOR_WRITE_RANGE(ctx, ptr, res); + if (res >= 0 && fd >= 0) + COMMON_INTERCEPTOR_FD_ACQUIRE(ctx, fd); + return res; +} +# define INIT_PREAD INTERCEPT_FUNCTION(pread) +#else +# define INIT_PREAD +#endif + +#if SANITIZER_INTERCEPT_PREAD64 +INTERCEPTOR(SSIZE_T, pread64, int fd, void *ptr, SIZE_T count, OFF64_T offset) { + void* ctx; + COMMON_INTERCEPTOR_ENTER(ctx, pread64, fd, ptr, count, offset); + SSIZE_T res = REAL(pread64)(fd, ptr, count, offset); + if (res > 0) + COMMON_INTERCEPTOR_WRITE_RANGE(ctx, ptr, res); + if (res >= 0 && fd >= 0) + COMMON_INTERCEPTOR_FD_ACQUIRE(ctx, fd); + return res; +} +# define INIT_PREAD64 INTERCEPT_FUNCTION(pread64) +#else +# define INIT_PREAD64 +#endif + +#if SANITIZER_INTERCEPT_WRITE +INTERCEPTOR(SSIZE_T, write, int fd, void *ptr, SIZE_T count) { + void* ctx; + COMMON_INTERCEPTOR_ENTER(ctx, write, fd, ptr, count); + if (fd >= 0) + COMMON_INTERCEPTOR_FD_RELEASE(ctx, fd); + SSIZE_T res = REAL(write)(fd, ptr, count); + if (res > 0) + COMMON_INTERCEPTOR_READ_RANGE(ctx, ptr, res); + return res; +} +# define INIT_WRITE INTERCEPT_FUNCTION(write) +#else +# define INIT_WRITE +#endif + +#if SANITIZER_INTERCEPT_PWRITE +INTERCEPTOR(SSIZE_T, pwrite, int fd, void *ptr, SIZE_T count) { + void* ctx; + COMMON_INTERCEPTOR_ENTER(ctx, pwrite, fd, ptr, count); + if (fd >= 0) + COMMON_INTERCEPTOR_FD_RELEASE(ctx, fd); + SSIZE_T res = REAL(pwrite)(fd, ptr, count); + if (res > 0) + COMMON_INTERCEPTOR_READ_RANGE(ctx, ptr, res); + return res; +} +# define INIT_PWRITE INTERCEPT_FUNCTION(pwrite) +#else +# define INIT_PWRITE +#endif + +#if SANITIZER_INTERCEPT_PWRITE64 +INTERCEPTOR(SSIZE_T, pwrite64, int fd, void *ptr, OFF64_T count) { + void* ctx; + COMMON_INTERCEPTOR_ENTER(ctx, pwrite64, fd, ptr, count); + if (fd >= 0) + COMMON_INTERCEPTOR_FD_RELEASE(ctx, fd); + SSIZE_T res = REAL(pwrite64)(fd, ptr, count); + if (res > 0) + COMMON_INTERCEPTOR_READ_RANGE(ctx, ptr, res); + return res; +} +# define INIT_PWRITE64 INTERCEPT_FUNCTION(pwrite64) +#else +# define INIT_PWRITE64 +#endif + +#if SANITIZER_INTERCEPT_PRCTL +INTERCEPTOR(int, prctl, int option, + unsigned long arg2, unsigned long arg3, // NOLINT + unsigned long arg4, unsigned long arg5) { // NOLINT + void* ctx; + COMMON_INTERCEPTOR_ENTER(ctx, prctl, option, arg2, arg3, arg4, arg5); + static const int PR_SET_NAME = 15; + int res = REAL(prctl(option, arg2, arg3, arg4, arg5)); + if (option == PR_SET_NAME) { + char buff[16]; + internal_strncpy(buff, (char*)arg2, 15); + buff[15] = 0; + COMMON_INTERCEPTOR_SET_THREAD_NAME(ctx, buff); + } + return res; +} +# define INIT_PRCTL INTERCEPT_FUNCTION(prctl) +#else +# define INIT_PRCTL +#endif // SANITIZER_INTERCEPT_PRCTL + + +#if SANITIZER_INTERCEPT_SCANF + +#include "sanitizer_common_interceptors_scanf.inc" + +INTERCEPTOR(int, vscanf, const char *format, va_list ap) { // NOLINT + void* ctx; + COMMON_INTERCEPTOR_ENTER(ctx, vscanf, format, ap); + scanf_common(ctx, format, ap); + int res = REAL(vscanf)(format, ap); // NOLINT + return res; +} + +INTERCEPTOR(int, vsscanf, const char *str, const char *format, // NOLINT + va_list ap) { + void* ctx; + COMMON_INTERCEPTOR_ENTER(ctx, vsscanf, str, format, ap); + scanf_common(ctx, format, ap); + int res = REAL(vsscanf)(str, format, ap); // NOLINT + // FIXME: read of str + return res; +} + +INTERCEPTOR(int, vfscanf, void *stream, const char *format, // NOLINT + va_list ap) { + void* ctx; + COMMON_INTERCEPTOR_ENTER(ctx, vfscanf, stream, format, ap); + scanf_common(ctx, format, ap); + int res = REAL(vfscanf)(stream, format, ap); // NOLINT + return res; +} + +INTERCEPTOR(int, scanf, const char *format, ...) { // NOLINT + void* ctx; + COMMON_INTERCEPTOR_ENTER(ctx, scanf, format); + va_list ap; + va_start(ap, format); + int res = vscanf(format, ap); // NOLINT + va_end(ap); + return res; +} + +INTERCEPTOR(int, fscanf, void* stream, const char *format, ...) { // NOLINT + void* ctx; + COMMON_INTERCEPTOR_ENTER(ctx, fscanf, stream, format); + va_list ap; + va_start(ap, format); + int res = vfscanf(stream, format, ap); // NOLINT + va_end(ap); + return res; +} + +INTERCEPTOR(int, sscanf, const char *str, const char *format, ...) { // NOLINT + void* ctx; + COMMON_INTERCEPTOR_ENTER(ctx, sscanf, str, format); // NOLINT + va_list ap; + va_start(ap, format); + int res = vsscanf(str, format, ap); // NOLINT + va_end(ap); + return res; +} + +#define INIT_SCANF \ + INTERCEPT_FUNCTION(scanf); \ + INTERCEPT_FUNCTION(sscanf); /* NOLINT */ \ + INTERCEPT_FUNCTION(fscanf); \ + INTERCEPT_FUNCTION(vscanf); \ + INTERCEPT_FUNCTION(vsscanf); \ + INTERCEPT_FUNCTION(vfscanf) + +#else +#define INIT_SCANF +#endif + +#define SANITIZER_COMMON_INTERCEPTORS_INIT \ + INIT_READ; \ + INIT_PREAD; \ + INIT_PREAD64; \ + INIT_PRCTL; \ + INIT_WRITE; \ + INIT_SCANF; diff --git a/lib/sanitizer_common/sanitizer_common_interceptors_scanf.inc b/lib/sanitizer_common/sanitizer_common_interceptors_scanf.inc new file mode 100644 index 000000000000..63d67a7115ec --- /dev/null +++ b/lib/sanitizer_common/sanitizer_common_interceptors_scanf.inc @@ -0,0 +1,142 @@ +//===-- sanitizer_common_interceptors_scanf.inc -----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Scanf implementation for use in *Sanitizer interceptors. +// +//===----------------------------------------------------------------------===// +#include <stdarg.h> + +struct ScanfSpec { + char c; + unsigned size; +}; + +// One-letter specs. +static const ScanfSpec scanf_specs[] = { + {'p', sizeof(void *)}, + {'e', sizeof(float)}, + {'E', sizeof(float)}, + {'a', sizeof(float)}, + {'f', sizeof(float)}, + {'g', sizeof(float)}, + {'d', sizeof(int)}, + {'i', sizeof(int)}, + {'o', sizeof(int)}, + {'u', sizeof(int)}, + {'x', sizeof(int)}, + {'X', sizeof(int)}, + {'n', sizeof(int)}, + {'t', sizeof(PTRDIFF_T)}, + {'z', sizeof(SIZE_T)}, + {'j', sizeof(INTMAX_T)}, + {'h', sizeof(short)} +}; + +static const unsigned scanf_specs_cnt = + sizeof(scanf_specs) / sizeof(scanf_specs[0]); + +// %ll?, %L?, %q? specs +static const ScanfSpec scanf_llspecs[] = { + {'e', sizeof(long double)}, + {'f', sizeof(long double)}, + {'g', sizeof(long double)}, + {'d', sizeof(long long)}, + {'i', sizeof(long long)}, + {'o', sizeof(long long)}, + {'u', sizeof(long long)}, + {'x', sizeof(long long)} +}; + +static const unsigned scanf_llspecs_cnt = + sizeof(scanf_llspecs) / sizeof(scanf_llspecs[0]); + +// %l? specs +static const ScanfSpec scanf_lspecs[] = { + {'e', sizeof(double)}, + {'f', sizeof(double)}, + {'g', sizeof(double)}, + {'d', sizeof(long)}, + {'i', sizeof(long)}, + {'o', sizeof(long)}, + {'u', sizeof(long)}, + {'x', sizeof(long)}, + {'X', sizeof(long)}, +}; + +static const unsigned scanf_lspecs_cnt = + sizeof(scanf_lspecs) / sizeof(scanf_lspecs[0]); + +static unsigned match_spec(const struct ScanfSpec *spec, unsigned n, char c) { + for (unsigned i = 0; i < n; ++i) + if (spec[i].c == c) + return spec[i].size; + return 0; +} + +static void scanf_common(void *ctx, const char *format, va_list ap_const) { + va_list aq; + va_copy(aq, ap_const); + + const char *p = format; + unsigned size; + + while (*p) { + if (*p != '%') { + ++p; + continue; + } + ++p; + if (*p == '*' || *p == '%' || *p == 0) { + ++p; + continue; + } + if (*p == '0' || (*p >= '1' && *p <= '9')) { + size = internal_atoll(p); + // +1 for the \0 at the end + COMMON_INTERCEPTOR_WRITE_RANGE(ctx, va_arg(aq, void *), size + 1); + ++p; + continue; + } + + if (*p == 'L' || *p == 'q') { + ++p; + size = match_spec(scanf_llspecs, scanf_llspecs_cnt, *p); + COMMON_INTERCEPTOR_WRITE_RANGE(ctx, va_arg(aq, void *), size); + continue; + } + + if (*p == 'l') { + ++p; + if (*p == 'l') { + ++p; + size = match_spec(scanf_llspecs, scanf_llspecs_cnt, *p); + COMMON_INTERCEPTOR_WRITE_RANGE(ctx, va_arg(aq, void *), size); + continue; + } else { + size = match_spec(scanf_lspecs, scanf_lspecs_cnt, *p); + COMMON_INTERCEPTOR_WRITE_RANGE(ctx, va_arg(aq, void *), size); + continue; + } + } + + if (*p == 'h' && *(p + 1) == 'h') { + COMMON_INTERCEPTOR_WRITE_RANGE(ctx, va_arg(aq, void *), sizeof(char)); + p += 2; + continue; + } + + size = match_spec(scanf_specs, scanf_specs_cnt, *p); + if (size) { + COMMON_INTERCEPTOR_WRITE_RANGE(ctx, va_arg(aq, void *), size); + ++p; + continue; + } + } + va_end(aq); +} diff --git a/lib/sanitizer_common/sanitizer_flags.cc b/lib/sanitizer_common/sanitizer_flags.cc index cdeeb78d7a5d..eca910c08090 100644 --- a/lib/sanitizer_common/sanitizer_flags.cc +++ b/lib/sanitizer_common/sanitizer_flags.cc @@ -18,13 +18,14 @@ namespace __sanitizer { -static char *GetFlagValue(const char *env, const char *name) { +static bool GetFlagValue(const char *env, const char *name, + const char **value, int *value_length) { if (env == 0) - return 0; + return false; const char *pos = internal_strstr(env, name); const char *end; if (pos == 0) - return 0; + return false; pos += internal_strlen(name); if (pos[0] != '=') { end = pos; @@ -42,41 +43,55 @@ static char *GetFlagValue(const char *env, const char *name) { if (end == 0) end = pos + internal_strlen(pos); } - int len = end - pos; - char *f = (char*)InternalAlloc(len + 1); - internal_memcpy(f, pos, len); - f[len] = '\0'; - return f; + *value = pos; + *value_length = end - pos; + return true; +} + +static bool StartsWith(const char *flag, int flag_length, const char *value) { + if (!flag || !value) + return false; + int value_length = internal_strlen(value); + return (flag_length >= value_length) && + (0 == internal_strncmp(flag, value, value_length)); } void ParseFlag(const char *env, bool *flag, const char *name) { - char *val = GetFlagValue(env, name); - if (val == 0) + const char *value; + int value_length; + if (!GetFlagValue(env, name, &value, &value_length)) return; - if (0 == internal_strcmp(val, "0") || - 0 == internal_strcmp(val, "no") || - 0 == internal_strcmp(val, "false")) + if (StartsWith(value, value_length, "0") || + StartsWith(value, value_length, "no") || + StartsWith(value, value_length, "false")) *flag = false; - if (0 == internal_strcmp(val, "1") || - 0 == internal_strcmp(val, "yes") || - 0 == internal_strcmp(val, "true")) + if (StartsWith(value, value_length, "1") || + StartsWith(value, value_length, "yes") || + StartsWith(value, value_length, "true")) *flag = true; - InternalFree(val); } void ParseFlag(const char *env, int *flag, const char *name) { - char *val = GetFlagValue(env, name); - if (val == 0) + const char *value; + int value_length; + if (!GetFlagValue(env, name, &value, &value_length)) return; - *flag = internal_atoll(val); - InternalFree(val); + *flag = internal_atoll(value); } +static LowLevelAllocator allocator_for_flags; + void ParseFlag(const char *env, const char **flag, const char *name) { - const char *val = GetFlagValue(env, name); - if (val == 0) + const char *value; + int value_length; + if (!GetFlagValue(env, name, &value, &value_length)) return; - *flag = val; + // Copy the flag value. Don't use locks here, as flags are parsed at + // tool startup. + char *value_copy = (char*)(allocator_for_flags.Allocate(value_length + 1)); + internal_memcpy(value_copy, value, value_length); + value_copy[value_length] = '\0'; + *flag = value_copy; } } // namespace __sanitizer diff --git a/lib/sanitizer_common/sanitizer_internal_defs.h b/lib/sanitizer_common/sanitizer_internal_defs.h index b8cf61fad84a..7ff27338192a 100644 --- a/lib/sanitizer_common/sanitizer_internal_defs.h +++ b/lib/sanitizer_common/sanitizer_internal_defs.h @@ -13,7 +13,7 @@ #ifndef SANITIZER_DEFS_H #define SANITIZER_DEFS_H -#include "sanitizer_interface_defs.h" +#include "sanitizer/common_interface_defs.h" using namespace __sanitizer; // NOLINT // ----------- ATTENTION ------------- // This header should NOT include any other headers to avoid portability issues. @@ -24,8 +24,7 @@ using namespace __sanitizer; // NOLINT #define WEAK SANITIZER_WEAK_ATTRIBUTE // Platform-specific defs. -#if defined(_WIN32) -typedef unsigned long DWORD; // NOLINT +#if defined(_MSC_VER) # define ALWAYS_INLINE __declspec(forceinline) // FIXME(timurrrr): do we need this on Windows? # define ALIAS(x) @@ -35,7 +34,12 @@ typedef unsigned long DWORD; // NOLINT # define NORETURN __declspec(noreturn) # define THREADLOCAL __declspec(thread) # define NOTHROW -#else // _WIN32 +# define LIKELY(x) (x) +# define UNLIKELY(x) (x) +# define UNUSED +# define USED +# define PREFETCH(x) /* _mm_prefetch(x, _MM_HINT_NTA) */ +#else // _MSC_VER # define ALWAYS_INLINE __attribute__((always_inline)) # define ALIAS(x) __attribute__((alias(x))) # define ALIGNED(x) __attribute__((aligned(x))) @@ -43,22 +47,21 @@ typedef unsigned long DWORD; // NOLINT # define NOINLINE __attribute__((noinline)) # define NORETURN __attribute__((noreturn)) # define THREADLOCAL __thread -# ifdef __cplusplus -# define NOTHROW throw() -# else -# define NOTHROW __attribute__((__nothrow__)) -#endif -#endif // _WIN32 - -// We have no equivalent of these on Windows. -#ifndef _WIN32 +# define NOTHROW throw() # define LIKELY(x) __builtin_expect(!!(x), 1) # define UNLIKELY(x) __builtin_expect(!!(x), 0) # define UNUSED __attribute__((unused)) # define USED __attribute__((used)) -#endif +# if defined(__i386__) || defined(__x86_64__) +// __builtin_prefetch(x) generates prefetchnt0 on x86 +# define PREFETCH(x) __asm__("prefetchnta (%0)" : : "r" (x)) +# else +# define PREFETCH(x) __builtin_prefetch(x) +# endif +#endif // _MSC_VER #if defined(_WIN32) +typedef unsigned long DWORD; // NOLINT typedef DWORD thread_return_t; # define THREAD_CALLING_CONV __stdcall #else // _WIN32 @@ -67,15 +70,11 @@ typedef void* thread_return_t; #endif // _WIN32 typedef thread_return_t (THREAD_CALLING_CONV *thread_callback_t)(void* arg); -// If __WORDSIZE was undefined by the platform, define it in terms of the -// compiler built-ins __LP64__ and _WIN64. -#ifndef __WORDSIZE -# if __LP64__ || defined(_WIN64) -# define __WORDSIZE 64 -# else -# define __WORDSIZE 32 -# endif -#endif // __WORDSIZE +#if __LP64__ || defined(_WIN64) +# define SANITIZER_WORDSIZE 64 +#else +# define SANITIZER_WORDSIZE 32 +#endif // NOTE: Functions below must be defined in each run-time. namespace __sanitizer { @@ -130,23 +129,32 @@ void NORETURN CheckFailed(const char *file, int line, const char *cond, #define DCHECK_GE(a, b) #endif -#define UNIMPLEMENTED() CHECK("unimplemented" && 0) +#define UNREACHABLE(msg) do { \ + CHECK(0 && msg); \ + Die(); \ +} while (0) + +#define UNIMPLEMENTED() UNREACHABLE("unimplemented") #define COMPILER_CHECK(pred) IMPL_COMPILER_ASSERT(pred, __LINE__) +#define ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0])) + #define IMPL_PASTE(a, b) a##b #define IMPL_COMPILER_ASSERT(pred, line) \ - typedef char IMPL_PASTE(assertion_failed_##_, line)[2*(int)(pred)-1]; + typedef char IMPL_PASTE(assertion_failed_##_, line)[2*(int)(pred)-1] // Limits for integral types. We have to redefine it in case we don't // have stdint.h (like in Visual Studio 9). -#if __WORDSIZE == 64 +#undef __INT64_C +#undef __UINT64_C +#if SANITIZER_WORDSIZE == 64 # define __INT64_C(c) c ## L # define __UINT64_C(c) c ## UL #else # define __INT64_C(c) c ## LL # define __UINT64_C(c) c ## ULL -#endif // __WORDSIZE == 64 +#endif // SANITIZER_WORDSIZE == 64 #undef INT32_MIN #define INT32_MIN (-2147483647-1) #undef INT32_MAX @@ -160,4 +168,25 @@ void NORETURN CheckFailed(const char *file, int line, const char *cond, #undef UINT64_MAX #define UINT64_MAX (__UINT64_C(18446744073709551615)) +enum LinkerInitialized { LINKER_INITIALIZED = 0 }; + +#if !defined(_MSC_VER) || defined(__clang__) +# define GET_CALLER_PC() (uptr)__builtin_return_address(0) +# define GET_CURRENT_FRAME() (uptr)__builtin_frame_address(0) +#else +extern "C" void* _ReturnAddress(void); +# pragma intrinsic(_ReturnAddress) +# define GET_CALLER_PC() (uptr)_ReturnAddress() +// CaptureStackBackTrace doesn't need to know BP on Windows. +// FIXME: This macro is still used when printing error reports though it's not +// clear if the BP value is needed in the ASan reports on Windows. +# define GET_CURRENT_FRAME() (uptr)0xDEADBEEF +#endif + +#define HANDLE_EINTR(res, f) { \ + do { \ + res = (f); \ + } while (res == -1 && errno == EINTR); \ + } + #endif // SANITIZER_DEFS_H diff --git a/lib/sanitizer_common/sanitizer_lfstack.h b/lib/sanitizer_common/sanitizer_lfstack.h new file mode 100644 index 000000000000..c26e45db8f89 --- /dev/null +++ b/lib/sanitizer_common/sanitizer_lfstack.h @@ -0,0 +1,73 @@ +//===-- sanitizer_lfstack.h -=-----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Lock-free stack. +// Uses 32/17 bits as ABA-counter on 32/64-bit platforms. +// The memory passed to Push() must not be ever munmap'ed. +// The type T must contain T *next field. +// +//===----------------------------------------------------------------------===// + +#ifndef SANITIZER_LFSTACK_H +#define SANITIZER_LFSTACK_H + +#include "sanitizer_internal_defs.h" +#include "sanitizer_common.h" +#include "sanitizer_atomic.h" + +namespace __sanitizer { + +template<typename T> +struct LFStack { + void Clear() { + atomic_store(&head_, 0, memory_order_relaxed); + } + + bool Empty() const { + return (atomic_load(&head_, memory_order_relaxed) & kPtrMask) == 0; + } + + void Push(T *p) { + u64 cmp = atomic_load(&head_, memory_order_relaxed); + for (;;) { + u64 cnt = (cmp & kCounterMask) + kCounterInc; + u64 xch = (u64)(uptr)p | cnt; + p->next = (T*)(uptr)(cmp & kPtrMask); + if (atomic_compare_exchange_weak(&head_, &cmp, xch, + memory_order_release)) + break; + } + } + + T *Pop() { + u64 cmp = atomic_load(&head_, memory_order_acquire); + for (;;) { + T *cur = (T*)(uptr)(cmp & kPtrMask); + if (cur == 0) + return 0; + T *nxt = cur->next; + u64 cnt = (cmp & kCounterMask); + u64 xch = (u64)(uptr)nxt | cnt; + if (atomic_compare_exchange_weak(&head_, &cmp, xch, + memory_order_acquire)) + return cur; + } + } + + // private: + static const int kCounterBits = FIRST_32_SECOND_64(32, 17); + static const u64 kPtrMask = ((u64)-1) >> kCounterBits; + static const u64 kCounterMask = ~kPtrMask; + static const u64 kCounterInc = kPtrMask + 1; + + atomic_uint64_t head_; +}; +} + +#endif // #ifndef SANITIZER_LFSTACK_H diff --git a/lib/sanitizer_common/sanitizer_libc.cc b/lib/sanitizer_common/sanitizer_libc.cc index c4332423bf3a..349be35012dd 100644 --- a/lib/sanitizer_common/sanitizer_libc.cc +++ b/lib/sanitizer_common/sanitizer_libc.cc @@ -44,6 +44,23 @@ void *internal_memcpy(void *dest, const void *src, uptr n) { return dest; } +void *internal_memmove(void *dest, const void *src, uptr n) { + char *d = (char*)dest; + char *s = (char*)src; + sptr i, signed_n = (sptr)n; + CHECK_GE(signed_n, 0); + if (d < s) { + for (i = 0; i < signed_n; ++i) + d[i] = s[i]; + } else { + if (d > s && signed_n > 0) + for (i = signed_n - 1; i >= 0 ; --i) { + d[i] = s[i]; + } + } + return dest; +} + void *internal_memset(void* s, int c, uptr n) { // The next line prevents Clang from making a call to memset() instead of the // loop below. @@ -56,6 +73,15 @@ void *internal_memset(void* s, int c, uptr n) { return s; } +uptr internal_strcspn(const char *s, const char *reject) { + uptr i; + for (i = 0; s[i]; i++) { + if (internal_strchr(reject, s[i]) != 0) + return i; + } + return i; +} + char* internal_strdup(const char *s) { uptr len = internal_strlen(s); char *s2 = (char*)InternalAlloc(len + 1); @@ -179,4 +205,23 @@ s64 internal_simple_strtoll(const char *nptr, char **endptr, int base) { } } +bool mem_is_zero(const char *beg, uptr size) { + CHECK_LE(size, 1UL << FIRST_32_SECOND_64(30, 40)); // Sanity check. + const char *end = beg + size; + uptr *aligned_beg = (uptr *)RoundUpTo((uptr)beg, sizeof(uptr)); + uptr *aligned_end = (uptr *)RoundDownTo((uptr)end, sizeof(uptr)); + uptr all = 0; + // Prologue. + for (const char *mem = beg; mem < (char*)aligned_beg && mem < end; mem++) + all |= *mem; + // Aligned loop. + for (; aligned_beg < aligned_end; aligned_beg++) + all |= *aligned_beg; + // Epilogue. + if ((char*)aligned_end >= beg) + for (const char *mem = (char*)aligned_end; mem < end; mem++) + all |= *mem; + return all == 0; +} + } // namespace __sanitizer diff --git a/lib/sanitizer_common/sanitizer_libc.h b/lib/sanitizer_common/sanitizer_libc.h index 8da4286cef73..aa052c654d39 100644 --- a/lib/sanitizer_common/sanitizer_libc.h +++ b/lib/sanitizer_common/sanitizer_libc.h @@ -18,7 +18,7 @@ // ----------- ATTENTION ------------- // This header should NOT include any other headers from sanitizer runtime. -#include "sanitizer_interface_defs.h" +#include "sanitizer/common_interface_defs.h" namespace __sanitizer { @@ -29,10 +29,12 @@ s64 internal_atoll(const char *nptr); void *internal_memchr(const void *s, int c, uptr n); int internal_memcmp(const void* s1, const void* s2, uptr n); void *internal_memcpy(void *dest, const void *src, uptr n); +void *internal_memmove(void *dest, const void *src, uptr n); // Should not be used in performance-critical places. void *internal_memset(void *s, int c, uptr n); char* internal_strchr(const char *s, int c); int internal_strcmp(const char *s1, const char *s2); +uptr internal_strcspn(const char *s, const char *reject); char *internal_strdup(const char *s); uptr internal_strlen(const char *s); char *internal_strncat(char *dst, const char *src, uptr n); @@ -45,6 +47,11 @@ char *internal_strstr(const char *haystack, const char *needle); // Works only for base=10 and doesn't set errno. s64 internal_simple_strtoll(const char *nptr, char **endptr, int base); +// Return true if all bytes in [mem, mem+size) are zero. +// Optimized for the case when the result is true. +bool mem_is_zero(const char *mem, uptr size); + + // Memory void *internal_mmap(void *addr, uptr length, int prot, int flags, int fd, u64 offset); @@ -53,12 +60,17 @@ int internal_munmap(void *addr, uptr length); // I/O typedef int fd_t; const fd_t kInvalidFd = -1; +const fd_t kStdinFd = 0; +const fd_t kStdoutFd = 1; +const fd_t kStderrFd = 2; int internal_close(fd_t fd); +int internal_isatty(fd_t fd); fd_t internal_open(const char *filename, bool write); uptr internal_read(fd_t fd, void *buf, uptr count); uptr internal_write(fd_t fd, const void *buf, uptr count); uptr internal_filesize(fd_t fd); // -1 on error. int internal_dup2(int oldfd, int newfd); +uptr internal_readlink(const char *path, char *buf, uptr bufsize); int internal_snprintf(char *buffer, uptr length, const char *format, ...); // Threading diff --git a/lib/sanitizer_common/sanitizer_linux.cc b/lib/sanitizer_common/sanitizer_linux.cc index 70e2eb346183..8b9ba38ca777 100644 --- a/lib/sanitizer_common/sanitizer_linux.cc +++ b/lib/sanitizer_common/sanitizer_linux.cc @@ -16,13 +16,12 @@ #include "sanitizer_common.h" #include "sanitizer_internal_defs.h" #include "sanitizer_libc.h" +#include "sanitizer_mutex.h" #include "sanitizer_placement_new.h" #include "sanitizer_procmaps.h" -#include "sanitizer_symbolizer.h" +#include "sanitizer_stacktrace.h" -#include <elf.h> #include <fcntl.h> -#include <link.h> #include <pthread.h> #include <sched.h> #include <sys/mman.h> @@ -32,13 +31,26 @@ #include <sys/time.h> #include <sys/types.h> #include <unistd.h> +#include <unwind.h> +#include <errno.h> +#include <sys/prctl.h> +#include <linux/futex.h> + +// Are we using 32-bit or 64-bit syscalls? +// x32 (which defines __x86_64__) has SANITIZER_WORDSIZE == 32 +// but it still needs to use 64-bit syscalls. +#if defined(__x86_64__) || SANITIZER_WORDSIZE == 64 +# define SANITIZER_LINUX_USES_64BIT_SYSCALLS 1 +#else +# define SANITIZER_LINUX_USES_64BIT_SYSCALLS 0 +#endif namespace __sanitizer { // --------------- sanitizer_libc.h void *internal_mmap(void *addr, uptr length, int prot, int flags, int fd, u64 offset) { -#if __WORDSIZE == 64 +#if SANITIZER_LINUX_USES_64BIT_SYSCALLS return (void *)syscall(__NR_mmap, addr, length, prot, flags, fd, offset); #else return (void *)syscall(__NR_mmap2, addr, length, prot, flags, fd, offset); @@ -59,15 +71,19 @@ fd_t internal_open(const char *filename, bool write) { } uptr internal_read(fd_t fd, void *buf, uptr count) { - return (uptr)syscall(__NR_read, fd, buf, count); + sptr res; + HANDLE_EINTR(res, (sptr)syscall(__NR_read, fd, buf, count)); + return res; } uptr internal_write(fd_t fd, const void *buf, uptr count) { - return (uptr)syscall(__NR_write, fd, buf, count); + sptr res; + HANDLE_EINTR(res, (sptr)syscall(__NR_write, fd, buf, count)); + return res; } uptr internal_filesize(fd_t fd) { -#if __WORDSIZE == 64 +#if SANITIZER_LINUX_USES_64BIT_SYSCALLS struct stat st; if (syscall(__NR_fstat, fd, &st)) return -1; @@ -83,11 +99,33 @@ int internal_dup2(int oldfd, int newfd) { return syscall(__NR_dup2, oldfd, newfd); } +uptr internal_readlink(const char *path, char *buf, uptr bufsize) { + return (uptr)syscall(__NR_readlink, path, buf, bufsize); +} + int internal_sched_yield() { return syscall(__NR_sched_yield); } // ----------------- sanitizer_common.h +bool FileExists(const char *filename) { +#if SANITIZER_LINUX_USES_64BIT_SYSCALLS + struct stat st; + if (syscall(__NR_stat, filename, &st)) + return false; +#else + struct stat64 st; + if (syscall(__NR_stat64, filename, &st)) + return false; +#endif + // Sanity check: filename is a regular file. + return S_ISREG(st.st_mode); +} + +uptr GetTid() { + return syscall(__NR_gettid); +} + void GetThreadStackTopAndBottom(bool at_initialization, uptr *stack_top, uptr *stack_bottom) { static const uptr kMaxThreadStackSize = 256 * (1 << 20); // 256M @@ -99,7 +137,7 @@ void GetThreadStackTopAndBottom(bool at_initialization, uptr *stack_top, CHECK_EQ(getrlimit(RLIMIT_STACK, &rl), 0); // Find the mapping that contains a stack variable. - ProcessMaps proc_maps; + MemoryMappingLayout proc_maps; uptr start, end, offset; uptr prev_end = 0; while (proc_maps.Next(&start, &end, &offset, 0, 0)) { @@ -163,102 +201,96 @@ const char *GetEnv(const char *name) { return 0; // Not found. } -// ------------------ sanitizer_symbolizer.h -typedef ElfW(Ehdr) Elf_Ehdr; -typedef ElfW(Shdr) Elf_Shdr; -typedef ElfW(Phdr) Elf_Phdr; - -bool FindDWARFSection(uptr object_file_addr, const char *section_name, - DWARFSection *section) { - Elf_Ehdr *exe = (Elf_Ehdr*)object_file_addr; - Elf_Shdr *sections = (Elf_Shdr*)(object_file_addr + exe->e_shoff); - uptr section_names = object_file_addr + - sections[exe->e_shstrndx].sh_offset; - for (int i = 0; i < exe->e_shnum; i++) { - Elf_Shdr *current_section = §ions[i]; - const char *current_name = (const char*)section_names + - current_section->sh_name; - if (IsFullNameOfDWARFSection(current_name, section_name)) { - section->data = (const char*)object_file_addr + - current_section->sh_offset; - section->size = current_section->sh_size; - return true; +static void ReadNullSepFileToArray(const char *path, char ***arr, + int arr_size) { + char *buff; + uptr buff_size = 0; + *arr = (char **)MmapOrDie(arr_size * sizeof(char *), "NullSepFileArray"); + ReadFileToBuffer(path, &buff, &buff_size, 1024 * 1024); + (*arr)[0] = buff; + int count, i; + for (count = 1, i = 1; ; i++) { + if (buff[i] == 0) { + if (buff[i+1] == 0) break; + (*arr)[count] = &buff[i+1]; + CHECK_LE(count, arr_size - 1); // FIXME: make this more flexible. + count++; } } - return false; + (*arr)[count] = 0; } -#ifdef ANDROID -uptr GetListOfModules(ModuleDIContext *modules, uptr max_modules) { - UNIMPLEMENTED(); +void ReExec() { + static const int kMaxArgv = 100, kMaxEnvp = 1000; + char **argv, **envp; + ReadNullSepFileToArray("/proc/self/cmdline", &argv, kMaxArgv); + ReadNullSepFileToArray("/proc/self/environ", &envp, kMaxEnvp); + execve(argv[0], argv, envp); } -#else // ANDROID -struct DlIteratePhdrData { - ModuleDIContext *modules; - uptr current_n; - uptr max_n; -}; -static const uptr kMaxPathLength = 512; - -static int dl_iterate_phdr_cb(dl_phdr_info *info, size_t size, void *arg) { - DlIteratePhdrData *data = (DlIteratePhdrData*)arg; - if (data->current_n == data->max_n) - return 0; - char *module_name = 0; - if (data->current_n == 0) { - // First module is the binary itself. - module_name = (char*)InternalAlloc(kMaxPathLength); - uptr module_name_len = readlink("/proc/self/exe", - module_name, kMaxPathLength); - CHECK_NE(module_name_len, (uptr)-1); - CHECK_LT(module_name_len, kMaxPathLength); - module_name[module_name_len] = '\0'; - } else if (info->dlpi_name) { - module_name = internal_strdup(info->dlpi_name); - } - if (module_name == 0 || module_name[0] == '\0') - return 0; - void *mem = &data->modules[data->current_n]; - ModuleDIContext *cur_module = new(mem) ModuleDIContext(module_name, - info->dlpi_addr); - data->current_n++; - for (int i = 0; i < info->dlpi_phnum; i++) { - const Elf_Phdr *phdr = &info->dlpi_phdr[i]; - if (phdr->p_type == PT_LOAD) { - uptr cur_beg = info->dlpi_addr + phdr->p_vaddr; - uptr cur_end = cur_beg + phdr->p_memsz; - cur_module->addAddressRange(cur_beg, cur_end); - } +void PrepareForSandboxing() { + // Some kinds of sandboxes may forbid filesystem access, so we won't be able + // to read the file mappings from /proc/self/maps. Luckily, neither the + // process will be able to load additional libraries, so it's fine to use the + // cached mappings. + MemoryMappingLayout::CacheMemoryMappings(); +} + +// ----------------- sanitizer_procmaps.h +// Linker initialized. +ProcSelfMapsBuff MemoryMappingLayout::cached_proc_self_maps_; +StaticSpinMutex MemoryMappingLayout::cache_lock_; // Linker initialized. + +MemoryMappingLayout::MemoryMappingLayout() { + proc_self_maps_.len = + ReadFileToBuffer("/proc/self/maps", &proc_self_maps_.data, + &proc_self_maps_.mmaped_size, 1 << 26); + if (proc_self_maps_.mmaped_size == 0) { + LoadFromCache(); + CHECK_GT(proc_self_maps_.len, 0); } - InternalFree(module_name); - return 0; + // internal_write(2, proc_self_maps_.data, proc_self_maps_.len); + Reset(); + // FIXME: in the future we may want to cache the mappings on demand only. + CacheMemoryMappings(); } -uptr GetListOfModules(ModuleDIContext *modules, uptr max_modules) { - CHECK(modules); - DlIteratePhdrData data = {modules, 0, max_modules}; - dl_iterate_phdr(dl_iterate_phdr_cb, &data); - return data.current_n; +MemoryMappingLayout::~MemoryMappingLayout() { + // Only unmap the buffer if it is different from the cached one. Otherwise + // it will be unmapped when the cache is refreshed. + if (proc_self_maps_.data != cached_proc_self_maps_.data) { + UnmapOrDie(proc_self_maps_.data, proc_self_maps_.mmaped_size); + } } -#endif // ANDROID -// ----------------- sanitizer_procmaps.h -ProcessMaps::ProcessMaps() { - proc_self_maps_buff_len_ = - ReadFileToBuffer("/proc/self/maps", &proc_self_maps_buff_, - &proc_self_maps_buff_mmaped_size_, 1 << 26); - CHECK_GT(proc_self_maps_buff_len_, 0); - // internal_write(2, proc_self_maps_buff_, proc_self_maps_buff_len_); - Reset(); +void MemoryMappingLayout::Reset() { + current_ = proc_self_maps_.data; } -ProcessMaps::~ProcessMaps() { - UnmapOrDie(proc_self_maps_buff_, proc_self_maps_buff_mmaped_size_); +// static +void MemoryMappingLayout::CacheMemoryMappings() { + SpinMutexLock l(&cache_lock_); + // Don't invalidate the cache if the mappings are unavailable. + ProcSelfMapsBuff old_proc_self_maps; + old_proc_self_maps = cached_proc_self_maps_; + cached_proc_self_maps_.len = + ReadFileToBuffer("/proc/self/maps", &cached_proc_self_maps_.data, + &cached_proc_self_maps_.mmaped_size, 1 << 26); + if (cached_proc_self_maps_.mmaped_size == 0) { + cached_proc_self_maps_ = old_proc_self_maps; + } else { + if (old_proc_self_maps.mmaped_size) { + UnmapOrDie(old_proc_self_maps.data, + old_proc_self_maps.mmaped_size); + } + } } -void ProcessMaps::Reset() { - current_ = proc_self_maps_buff_; +void MemoryMappingLayout::LoadFromCache() { + SpinMutexLock l(&cache_lock_); + if (cached_proc_self_maps_.data) { + proc_self_maps_ = cached_proc_self_maps_; + } } // Parse a hex value in str and update str. @@ -290,9 +322,9 @@ static bool IsDecimal(char c) { return c >= '0' && c <= '9'; } -bool ProcessMaps::Next(uptr *start, uptr *end, uptr *offset, - char filename[], uptr filename_size) { - char *last = proc_self_maps_buff_ + proc_self_maps_buff_len_; +bool MemoryMappingLayout::Next(uptr *start, uptr *end, uptr *offset, + char filename[], uptr filename_size) { + char *last = proc_self_maps_.data + proc_self_maps_.len; if (current_ >= last) return false; uptr dummy; if (!start) start = &dummy; @@ -336,13 +368,116 @@ bool ProcessMaps::Next(uptr *start, uptr *end, uptr *offset, return true; } -// Gets the object name and the offset by walking ProcessMaps. -bool ProcessMaps::GetObjectNameAndOffset(uptr addr, uptr *offset, - char filename[], - uptr filename_size) { +// Gets the object name and the offset by walking MemoryMappingLayout. +bool MemoryMappingLayout::GetObjectNameAndOffset(uptr addr, uptr *offset, + char filename[], + uptr filename_size) { return IterateForObjectNameAndOffset(addr, offset, filename, filename_size); } +bool SanitizerSetThreadName(const char *name) { +#ifdef PR_SET_NAME + return 0 == prctl(PR_SET_NAME, (unsigned long)name, 0, 0, 0); // NOLINT +#else + return false; +#endif +} + +bool SanitizerGetThreadName(char *name, int max_len) { +#ifdef PR_GET_NAME + char buff[17]; + if (prctl(PR_GET_NAME, (unsigned long)buff, 0, 0, 0)) // NOLINT + return false; + internal_strncpy(name, buff, max_len); + name[max_len] = 0; + return true; +#else + return false; +#endif +} + +#ifndef SANITIZER_GO +//------------------------- SlowUnwindStack ----------------------------------- +#ifdef __arm__ +#define UNWIND_STOP _URC_END_OF_STACK +#define UNWIND_CONTINUE _URC_NO_REASON +#else +#define UNWIND_STOP _URC_NORMAL_STOP +#define UNWIND_CONTINUE _URC_NO_REASON +#endif + +uptr Unwind_GetIP(struct _Unwind_Context *ctx) { +#ifdef __arm__ + uptr val; + _Unwind_VRS_Result res = _Unwind_VRS_Get(ctx, _UVRSC_CORE, + 15 /* r15 = PC */, _UVRSD_UINT32, &val); + CHECK(res == _UVRSR_OK && "_Unwind_VRS_Get failed"); + // Clear the Thumb bit. + return val & ~(uptr)1; +#else + return _Unwind_GetIP(ctx); +#endif +} + +_Unwind_Reason_Code Unwind_Trace(struct _Unwind_Context *ctx, void *param) { + StackTrace *b = (StackTrace*)param; + CHECK(b->size < b->max_size); + uptr pc = Unwind_GetIP(ctx); + b->trace[b->size++] = pc; + if (b->size == b->max_size) return UNWIND_STOP; + return UNWIND_CONTINUE; +} + +static bool MatchPc(uptr cur_pc, uptr trace_pc) { + return cur_pc - trace_pc <= 64 || trace_pc - cur_pc <= 64; +} + +void StackTrace::SlowUnwindStack(uptr pc, uptr max_depth) { + this->size = 0; + this->max_size = max_depth; + if (max_depth > 1) { + _Unwind_Backtrace(Unwind_Trace, this); + // We need to pop a few frames so that pc is on top. + // trace[0] belongs to the current function so we always pop it. + int to_pop = 1; + /**/ if (size > 1 && MatchPc(pc, trace[1])) to_pop = 1; + else if (size > 2 && MatchPc(pc, trace[2])) to_pop = 2; + else if (size > 3 && MatchPc(pc, trace[3])) to_pop = 3; + else if (size > 4 && MatchPc(pc, trace[4])) to_pop = 4; + else if (size > 5 && MatchPc(pc, trace[5])) to_pop = 5; + this->PopStackFrames(to_pop); + } + this->trace[0] = pc; +} + +#endif // #ifndef SANITIZER_GO + +enum MutexState { + MtxUnlocked = 0, + MtxLocked = 1, + MtxSleeping = 2 +}; + +BlockingMutex::BlockingMutex(LinkerInitialized) { + CHECK_EQ(owner_, 0); +} + +void BlockingMutex::Lock() { + atomic_uint32_t *m = reinterpret_cast<atomic_uint32_t *>(&opaque_storage_); + if (atomic_exchange(m, MtxLocked, memory_order_acquire) == MtxUnlocked) + return; + while (atomic_exchange(m, MtxSleeping, memory_order_acquire) != MtxUnlocked) + syscall(__NR_futex, m, FUTEX_WAIT, MtxSleeping, 0, 0, 0); +} + +void BlockingMutex::Unlock() { + atomic_uint32_t *m = reinterpret_cast<atomic_uint32_t *>(&opaque_storage_); + u32 v = atomic_exchange(m, MtxUnlocked, memory_order_relaxed); + CHECK_NE(v, MtxUnlocked); + if (v == MtxSleeping) + syscall(__NR_futex, m, FUTEX_WAKE, 1, 0, 0, 0); +} + } // namespace __sanitizer #endif // __linux__ diff --git a/lib/sanitizer_common/sanitizer_list.h b/lib/sanitizer_common/sanitizer_list.h index ef98eee12317..f61d28f3d900 100644 --- a/lib/sanitizer_common/sanitizer_list.h +++ b/lib/sanitizer_common/sanitizer_list.h @@ -72,6 +72,8 @@ struct IntrusiveList { void append_front(IntrusiveList<Item> *l) { CHECK_NE(this, l); + if (l->empty()) + return; if (empty()) { *this = *l; } else if (!l->empty()) { @@ -84,6 +86,8 @@ struct IntrusiveList { void append_back(IntrusiveList<Item> *l) { CHECK_NE(this, l); + if (l->empty()) + return; if (empty()) { *this = *l; } else { diff --git a/lib/sanitizer_common/sanitizer_mac.cc b/lib/sanitizer_common/sanitizer_mac.cc index e64c2debb882..c4b8e4c2bcf2 100644 --- a/lib/sanitizer_common/sanitizer_mac.cc +++ b/lib/sanitizer_common/sanitizer_mac.cc @@ -18,7 +18,6 @@ #include "sanitizer_internal_defs.h" #include "sanitizer_libc.h" #include "sanitizer_procmaps.h" -#include "sanitizer_symbolizer.h" #include <crt_externs.h> // for _NSGetEnviron #include <fcntl.h> @@ -31,6 +30,7 @@ #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> +#include <libkern/OSAtomic.h> namespace __sanitizer { @@ -62,7 +62,7 @@ uptr internal_write(fd_t fd, const void *buf, uptr count) { } uptr internal_filesize(fd_t fd) { - struct stat st = {}; + struct stat st; if (fstat(fd, &st)) return -1; return (uptr)st.st_size; @@ -72,11 +72,27 @@ int internal_dup2(int oldfd, int newfd) { return dup2(oldfd, newfd); } +uptr internal_readlink(const char *path, char *buf, uptr bufsize) { + return readlink(path, buf, bufsize); +} + int internal_sched_yield() { return sched_yield(); } // ----------------- sanitizer_common.h +bool FileExists(const char *filename) { + struct stat st; + if (stat(filename, &st)) + return false; + // Sanity check: filename is a regular file. + return S_ISREG(st.st_mode); +} + +uptr GetTid() { + return reinterpret_cast<uptr>(pthread_self()); +} + void GetThreadStackTopAndBottom(bool at_initialization, uptr *stack_top, uptr *stack_bottom) { CHECK(stack_top); @@ -107,25 +123,21 @@ const char *GetEnv(const char *name) { return 0; } -// ------------------ sanitizer_symbolizer.h -bool FindDWARFSection(uptr object_file_addr, const char *section_name, - DWARFSection *section) { +void ReExec() { UNIMPLEMENTED(); - return false; } -uptr GetListOfModules(ModuleDIContext *modules, uptr max_modules) { - UNIMPLEMENTED(); - return 0; -}; +void PrepareForSandboxing() { + // Nothing here for now. +} // ----------------- sanitizer_procmaps.h -ProcessMaps::ProcessMaps() { +MemoryMappingLayout::MemoryMappingLayout() { Reset(); } -ProcessMaps::~ProcessMaps() { +MemoryMappingLayout::~MemoryMappingLayout() { } // More information about Mach-O headers can be found in mach-o/loader.h @@ -142,15 +154,26 @@ ProcessMaps::~ProcessMaps() { // Because these fields are taken from the images as is, one needs to add // _dyld_get_image_vmaddr_slide() to get the actual addresses at runtime. -void ProcessMaps::Reset() { +void MemoryMappingLayout::Reset() { // Count down from the top. // TODO(glider): as per man 3 dyld, iterating over the headers with // _dyld_image_count is thread-unsafe. We need to register callbacks for - // adding and removing images which will invalidate the ProcessMaps state. + // adding and removing images which will invalidate the MemoryMappingLayout + // state. current_image_ = _dyld_image_count(); current_load_cmd_count_ = -1; current_load_cmd_addr_ = 0; current_magic_ = 0; + current_filetype_ = 0; +} + +// static +void MemoryMappingLayout::CacheMemoryMappings() { + // No-op on Mac for now. +} + +void MemoryMappingLayout::LoadFromCache() { + // No-op on Mac for now. } // Next and NextSegmentLoad were inspired by base/sysinfo.cc in @@ -161,7 +184,7 @@ void ProcessMaps::Reset() { // segment. // Note that the segment addresses are not necessarily sorted. template<u32 kLCSegment, typename SegmentCommand> -bool ProcessMaps::NextSegmentLoad( +bool MemoryMappingLayout::NextSegmentLoad( uptr *start, uptr *end, uptr *offset, char filename[], uptr filename_size) { const char* lc = current_load_cmd_addr_; @@ -171,7 +194,13 @@ bool ProcessMaps::NextSegmentLoad( const SegmentCommand* sc = (const SegmentCommand *)lc; if (start) *start = sc->vmaddr + dlloff; if (end) *end = sc->vmaddr + sc->vmsize + dlloff; - if (offset) *offset = sc->fileoff; + if (offset) { + if (current_filetype_ == /*MH_EXECUTE*/ 0x2) { + *offset = sc->vmaddr; + } else { + *offset = sc->fileoff; + } + } if (filename) { internal_strncpy(filename, _dyld_get_image_name(current_image_), filename_size); @@ -181,8 +210,8 @@ bool ProcessMaps::NextSegmentLoad( return false; } -bool ProcessMaps::Next(uptr *start, uptr *end, uptr *offset, - char filename[], uptr filename_size) { +bool MemoryMappingLayout::Next(uptr *start, uptr *end, uptr *offset, + char filename[], uptr filename_size) { for (; current_image_ >= 0; current_image_--) { const mach_header* hdr = _dyld_get_image_header(current_image_); if (!hdr) continue; @@ -190,6 +219,7 @@ bool ProcessMaps::Next(uptr *start, uptr *end, uptr *offset, // Set up for this image; current_load_cmd_count_ = hdr->ncmds; current_magic_ = hdr->magic; + current_filetype_ = hdr->filetype; switch (current_magic_) { #ifdef MH_MAGIC_64 case MH_MAGIC_64: { @@ -232,12 +262,31 @@ bool ProcessMaps::Next(uptr *start, uptr *end, uptr *offset, return false; } -bool ProcessMaps::GetObjectNameAndOffset(uptr addr, uptr *offset, - char filename[], - uptr filename_size) { +bool MemoryMappingLayout::GetObjectNameAndOffset(uptr addr, uptr *offset, + char filename[], + uptr filename_size) { return IterateForObjectNameAndOffset(addr, offset, filename, filename_size); } +BlockingMutex::BlockingMutex(LinkerInitialized) { + // We assume that OS_SPINLOCK_INIT is zero +} + +void BlockingMutex::Lock() { + CHECK(sizeof(OSSpinLock) <= sizeof(opaque_storage_)); + CHECK(OS_SPINLOCK_INIT == 0); + CHECK(owner_ != (uptr)pthread_self()); + OSSpinLockLock((OSSpinLock*)&opaque_storage_); + CHECK(!owner_); + owner_ = (uptr)pthread_self(); +} + +void BlockingMutex::Unlock() { + CHECK(owner_ == (uptr)pthread_self()); + owner_ = 0; + OSSpinLockUnlock((OSSpinLock*)&opaque_storage_); +} + } // namespace __sanitizer #endif // __APPLE__ diff --git a/lib/sanitizer_common/sanitizer_mutex.h b/lib/sanitizer_common/sanitizer_mutex.h index ca3e2f9a4839..56438fce471c 100644 --- a/lib/sanitizer_common/sanitizer_mutex.h +++ b/lib/sanitizer_common/sanitizer_mutex.h @@ -20,18 +20,22 @@ namespace __sanitizer { -class SpinMutex { +class StaticSpinMutex { public: - SpinMutex() { + void Init() { atomic_store(&state_, 0, memory_order_relaxed); } void Lock() { - if (atomic_exchange(&state_, 1, memory_order_acquire) == 0) + if (TryLock()) return; LockSlow(); } + bool TryLock() { + return atomic_exchange(&state_, 1, memory_order_acquire) == 0; + } + void Unlock() { atomic_store(&state_, 0, memory_order_release); } @@ -50,11 +54,29 @@ class SpinMutex { return; } } +}; +class SpinMutex : public StaticSpinMutex { + public: + SpinMutex() { + Init(); + } + + private: SpinMutex(const SpinMutex&); void operator=(const SpinMutex&); }; +class BlockingMutex { + public: + explicit BlockingMutex(LinkerInitialized); + void Lock(); + void Unlock(); + private: + uptr opaque_storage_[10]; + uptr owner_; // for debugging +}; + template<typename MutexType> class GenericScopedLock { public: @@ -93,7 +115,8 @@ class GenericScopedReadLock { void operator=(const GenericScopedReadLock&); }; -typedef GenericScopedLock<SpinMutex> SpinMutexLock; +typedef GenericScopedLock<StaticSpinMutex> SpinMutexLock; +typedef GenericScopedLock<BlockingMutex> BlockingMutexLock; } // namespace __sanitizer diff --git a/lib/sanitizer_common/sanitizer_placement_new.h b/lib/sanitizer_common/sanitizer_placement_new.h index f133a6ffe513..c0b85e1c1717 100644 --- a/lib/sanitizer_common/sanitizer_placement_new.h +++ b/lib/sanitizer_common/sanitizer_placement_new.h @@ -19,7 +19,7 @@ #include "sanitizer_internal_defs.h" namespace __sanitizer { -#if (__WORDSIZE == 64) || defined(__APPLE__) +#if (SANITIZER_WORDSIZE == 64) || defined(__APPLE__) typedef uptr operator_new_ptr_type; #else typedef u32 operator_new_ptr_type; diff --git a/lib/sanitizer_common/sanitizer_platform_interceptors.h b/lib/sanitizer_common/sanitizer_platform_interceptors.h new file mode 100644 index 000000000000..abd41fe8c997 --- /dev/null +++ b/lib/sanitizer_common/sanitizer_platform_interceptors.h @@ -0,0 +1,38 @@ +//===-- sanitizer_platform_interceptors.h -----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines macro telling whether sanitizer tools can/should intercept +// given library functions on a given platform. +// +//===----------------------------------------------------------------------===// + +#include "sanitizer_internal_defs.h" + +#if !defined(_WIN32) +# define SI_NOT_WINDOWS 1 +#else +# define SI_NOT_WINDOWS 0 +#endif + +#if defined(__linux__) && !defined(ANDROID) +# define SI_LINUX_NOT_ANDROID 1 +#else +# define SI_LINUX_NOT_ANDROID 0 +#endif + +# define SANITIZER_INTERCEPT_READ SI_NOT_WINDOWS +# define SANITIZER_INTERCEPT_PREAD SI_NOT_WINDOWS +# define SANITIZER_INTERCEPT_WRITE SI_NOT_WINDOWS +# define SANITIZER_INTERCEPT_PWRITE SI_NOT_WINDOWS + +# define SANITIZER_INTERCEPT_PREAD64 SI_LINUX_NOT_ANDROID +# define SANITIZER_INTERCEPT_PWRITE64 SI_LINUX_NOT_ANDROID +# define SANITIZER_INTERCEPT_PRCTL SI_LINUX_NOT_ANDROID + +# define SANITIZER_INTERCEPT_SCANF 1 diff --git a/lib/sanitizer_common/sanitizer_posix.cc b/lib/sanitizer_common/sanitizer_posix.cc index 4caee3ba68c0..32657838600d 100644 --- a/lib/sanitizer_common/sanitizer_posix.cc +++ b/lib/sanitizer_common/sanitizer_posix.cc @@ -17,10 +17,12 @@ #include "sanitizer_libc.h" #include "sanitizer_procmaps.h" +#include <errno.h> #include <pthread.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> +#include <string.h> #include <sys/mman.h> #include <sys/resource.h> #include <sys/time.h> @@ -30,6 +32,13 @@ namespace __sanitizer { // ------------- sanitizer_common.h +uptr GetPageSize() { + return sysconf(_SC_PAGESIZE); +} + +uptr GetMmapGranularity() { + return GetPageSize(); +} int GetPid() { return getpid(); @@ -40,13 +49,22 @@ uptr GetThreadSelf() { } void *MmapOrDie(uptr size, const char *mem_type) { - size = RoundUpTo(size, kPageSize); + size = RoundUpTo(size, GetPageSizeCached()); void *res = internal_mmap(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); if (res == (void*)-1) { - Report("ERROR: Failed to allocate 0x%zx (%zd) bytes of %s\n", - size, size, mem_type); + static int recursion_count; + if (recursion_count) { + // The Report() and CHECK calls below may call mmap recursively and fail. + // If we went into recursion, just die. + RawWrite("AddressSanitizer is unable to mmap\n"); + Die(); + } + recursion_count++; + Report("ERROR: Failed to allocate 0x%zx (%zd) bytes of %s: %s\n", + size, size, mem_type, strerror(errno)); + DumpProcessMap(); CHECK("unable to mmap" && 0); } return res; @@ -63,10 +81,31 @@ void UnmapOrDie(void *addr, uptr size) { } void *MmapFixedNoReserve(uptr fixed_addr, uptr size) { - return internal_mmap((void*)fixed_addr, size, - PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANON | MAP_FIXED | MAP_NORESERVE, - -1, 0); + uptr PageSize = GetPageSizeCached(); + void *p = internal_mmap((void*)(fixed_addr & ~(PageSize - 1)), + RoundUpTo(size, PageSize), + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON | MAP_FIXED | MAP_NORESERVE, + -1, 0); + if (p == (void*)-1) + Report("ERROR: Failed to allocate 0x%zx (%zd) bytes at address %p (%d)\n", + size, size, fixed_addr, errno); + return p; +} + +void *MmapFixedOrDie(uptr fixed_addr, uptr size) { + uptr PageSize = GetPageSizeCached(); + void *p = internal_mmap((void*)(fixed_addr & ~(PageSize - 1)), + RoundUpTo(size, PageSize), + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON | MAP_FIXED, + -1, 0); + if (p == (void*)-1) { + Report("ERROR: Failed to allocate 0x%zx (%zd) bytes at address %p (%d)\n", + size, size, fixed_addr, errno); + CHECK("unable to mmap" && 0); + } + return p; } void *Mprotect(uptr fixed_addr, uptr size) { @@ -76,13 +115,17 @@ void *Mprotect(uptr fixed_addr, uptr size) { -1, 0); } +void FlushUnneededShadowMemory(uptr addr, uptr size) { + madvise((void*)addr, size, MADV_DONTNEED); +} + void *MapFileToMemory(const char *file_name, uptr *buff_size) { fd_t fd = internal_open(file_name, false); CHECK_NE(fd, kInvalidFd); uptr fsize = internal_filesize(fd); CHECK_NE(fsize, (uptr)-1); CHECK_GT(fsize, 0); - *buff_size = RoundUpTo(fsize, kPageSize); + *buff_size = RoundUpTo(fsize, GetPageSizeCached()); void *map = internal_mmap(0, *buff_size, PROT_READ, MAP_PRIVATE, fd, 0); return (map == MAP_FAILED) ? 0 : map; } @@ -100,7 +143,7 @@ static inline bool IntervalsAreSeparate(uptr start1, uptr end1, // several worker threads on Mac, which aren't expected to map big chunks of // memory). bool MemoryRangeIsAvailable(uptr range_start, uptr range_end) { - ProcessMaps procmaps; + MemoryMappingLayout procmaps; uptr start, end; while (procmaps.Next(&start, &end, /*offset*/0, /*filename*/0, /*filename_size*/0)) { @@ -111,7 +154,7 @@ bool MemoryRangeIsAvailable(uptr range_start, uptr range_end) { } void DumpProcessMap() { - ProcessMaps proc_maps; + MemoryMappingLayout proc_maps; uptr start, end; const sptr kBufSize = 4095; char *filename = (char*)MmapOrDie(kBufSize, __FUNCTION__); @@ -135,6 +178,23 @@ void DisableCoreDumper() { setrlimit(RLIMIT_CORE, &nocore); } +bool StackSizeIsUnlimited() { + struct rlimit rlim; + CHECK_EQ(0, getrlimit(RLIMIT_STACK, &rlim)); + return (rlim.rlim_cur == (uptr)-1); +} + +void SetStackSizeLimitInBytes(uptr limit) { + struct rlimit rlim; + rlim.rlim_cur = limit; + rlim.rlim_max = limit; + if (setrlimit(RLIMIT_STACK, &rlim)) { + Report("setrlimit() failed %d\n", errno); + Die(); + } + CHECK(!StackSizeIsUnlimited()); +} + void SleepForSeconds(int seconds) { sleep(seconds); } @@ -159,6 +219,10 @@ int Atexit(void (*function)(void)) { #endif } +int internal_isatty(fd_t fd) { + return isatty(fd); +} + } // namespace __sanitizer #endif // __linux__ || __APPLE_ diff --git a/lib/sanitizer_common/sanitizer_printf.cc b/lib/sanitizer_common/sanitizer_printf.cc index 7b70c3aae23d..2393e8f2b87b 100644 --- a/lib/sanitizer_common/sanitizer_printf.cc +++ b/lib/sanitizer_common/sanitizer_printf.cc @@ -45,7 +45,12 @@ static int AppendUnsigned(char **buff, const char *buff_end, u64 num, num_buffer[pos++] = num % base; num /= base; } while (num > 0); - while (pos < minimal_num_length) num_buffer[pos++] = 0; + if (pos < minimal_num_length) { + // Make sure compiler doesn't insert call to memset here. + internal_memset(&num_buffer[pos], 0, + sizeof(num_buffer[0]) * (minimal_num_length - pos)); + pos = minimal_num_length; + } int result = 0; while (pos-- > 0) { uptr digit = num_buffer[pos]; @@ -55,13 +60,16 @@ static int AppendUnsigned(char **buff, const char *buff_end, u64 num, return result; } -static int AppendSignedDecimal(char **buff, const char *buff_end, s64 num) { +static int AppendSignedDecimal(char **buff, const char *buff_end, s64 num, + u8 minimal_num_length) { int result = 0; if (num < 0) { result += AppendChar(buff, buff_end, '-'); num = -num; + if (minimal_num_length) + --minimal_num_length; } - result += AppendUnsigned(buff, buff_end, (u64)num, 10, 0); + result += AppendUnsigned(buff, buff_end, (u64)num, 10, minimal_num_length); return result; } @@ -79,14 +87,14 @@ static int AppendPointer(char **buff, const char *buff_end, u64 ptr_value) { int result = 0; result += AppendString(buff, buff_end, "0x"); result += AppendUnsigned(buff, buff_end, ptr_value, 16, - (__WORDSIZE == 64) ? 12 : 8); + (SANITIZER_WORDSIZE == 64) ? 12 : 8); return result; } int VSNPrintf(char *buff, int buff_length, const char *format, va_list args) { - static const char *kPrintfFormatsHelp = "Supported Printf formats: " - "%%[z]{d,u,x}; %%p; %%s\n"; + static const char *kPrintfFormatsHelp = + "Supported Printf formats: %(0[0-9]*)?(z|ll)?{d,u,x}; %p; %s; %c\n"; RAW_CHECK(format); RAW_CHECK(buff_length > 0); const char *buff_end = &buff[buff_length - 1]; @@ -98,37 +106,55 @@ int VSNPrintf(char *buff, int buff_length, continue; } cur++; + bool have_width = (*cur == '0'); + int width = 0; + if (have_width) { + while (*cur >= '0' && *cur <= '9') { + have_width = true; + width = width * 10 + *cur++ - '0'; + } + } bool have_z = (*cur == 'z'); cur += have_z; + bool have_ll = !have_z && (cur[0] == 'l' && cur[1] == 'l'); + cur += have_ll * 2; s64 dval; u64 uval; + bool have_flags = have_width | have_z | have_ll; switch (*cur) { case 'd': { - dval = have_z ? va_arg(args, sptr) - : va_arg(args, int); - result += AppendSignedDecimal(&buff, buff_end, dval); + dval = have_ll ? va_arg(args, s64) + : have_z ? va_arg(args, sptr) + : va_arg(args, int); + result += AppendSignedDecimal(&buff, buff_end, dval, width); break; } case 'u': case 'x': { - uval = have_z ? va_arg(args, uptr) - : va_arg(args, unsigned); + uval = have_ll ? va_arg(args, u64) + : have_z ? va_arg(args, uptr) + : va_arg(args, unsigned); result += AppendUnsigned(&buff, buff_end, uval, - (*cur == 'u') ? 10 : 16, 0); + (*cur == 'u') ? 10 : 16, width); break; } case 'p': { - RAW_CHECK_MSG(!have_z, kPrintfFormatsHelp); + RAW_CHECK_MSG(!have_flags, kPrintfFormatsHelp); result += AppendPointer(&buff, buff_end, va_arg(args, uptr)); break; } case 's': { - RAW_CHECK_MSG(!have_z, kPrintfFormatsHelp); + RAW_CHECK_MSG(!have_flags, kPrintfFormatsHelp); result += AppendString(&buff, buff_end, va_arg(args, char*)); break; } + case 'c': { + RAW_CHECK_MSG(!have_flags, kPrintfFormatsHelp); + result += AppendChar(&buff, buff_end, va_arg(args, int)); + break; + } case '%' : { - RAW_CHECK_MSG(!have_z, kPrintfFormatsHelp); + RAW_CHECK_MSG(!have_flags, kPrintfFormatsHelp); result += AppendChar(&buff, buff_end, '%'); break; } @@ -142,16 +168,22 @@ int VSNPrintf(char *buff, int buff_length, return result; } +static void (*PrintfAndReportCallback)(const char *); +void SetPrintfAndReportCallback(void (*callback)(const char *)) { + PrintfAndReportCallback = callback; +} + void Printf(const char *format, ...) { - const int kLen = 1024 * 4; - char *buffer = (char*)MmapOrDie(kLen, __FUNCTION__); + const int kLen = 16 * 1024; + InternalScopedBuffer<char> buffer(kLen); va_list args; va_start(args, format); - int needed_length = VSNPrintf(buffer, kLen, format, args); + int needed_length = VSNPrintf(buffer.data(), kLen, format, args); va_end(args); RAW_CHECK_MSG(needed_length < kLen, "Buffer in Printf is too short!\n"); - RawWrite(buffer); - UnmapOrDie(buffer, kLen); + RawWrite(buffer.data()); + if (PrintfAndReportCallback) + PrintfAndReportCallback(buffer.data()); } // Writes at most "length" symbols to "buffer" (including trailing '\0'). @@ -168,18 +200,20 @@ int internal_snprintf(char *buffer, uptr length, const char *format, ...) { // Like Printf, but prints the current PID before the output string. void Report(const char *format, ...) { - const int kLen = 1024 * 4; - char *buffer = (char*)MmapOrDie(kLen, __FUNCTION__); - int needed_length = internal_snprintf(buffer, kLen, "==%d== ", GetPid()); + const int kLen = 16 * 1024; + InternalScopedBuffer<char> buffer(kLen); + int needed_length = internal_snprintf(buffer.data(), + kLen, "==%d== ", GetPid()); RAW_CHECK_MSG(needed_length < kLen, "Buffer in Report is too short!\n"); va_list args; va_start(args, format); - needed_length += VSNPrintf(buffer + needed_length, kLen - needed_length, - format, args); + needed_length += VSNPrintf(buffer.data() + needed_length, + kLen - needed_length, format, args); va_end(args); RAW_CHECK_MSG(needed_length < kLen, "Buffer in Report is too short!\n"); - RawWrite(buffer); - UnmapOrDie(buffer, kLen); + RawWrite(buffer.data()); + if (PrintfAndReportCallback) + PrintfAndReportCallback(buffer.data()); } } // namespace __sanitizer diff --git a/lib/sanitizer_common/sanitizer_procmaps.h b/lib/sanitizer_common/sanitizer_procmaps.h index e7f9cac6cf6c..1b8ea7aff165 100644 --- a/lib/sanitizer_common/sanitizer_procmaps.h +++ b/lib/sanitizer_common/sanitizer_procmaps.h @@ -15,12 +15,32 @@ #define SANITIZER_PROCMAPS_H #include "sanitizer_internal_defs.h" +#include "sanitizer_mutex.h" namespace __sanitizer { -class ProcessMaps { +#ifdef _WIN32 +class MemoryMappingLayout { public: - ProcessMaps(); + MemoryMappingLayout() {} + bool GetObjectNameAndOffset(uptr addr, uptr *offset, + char filename[], uptr filename_size) { + UNIMPLEMENTED(); + } +}; + +#else // _WIN32 +#if defined(__linux__) +struct ProcSelfMapsBuff { + char *data; + uptr mmaped_size; + uptr len; +}; +#endif // defined(__linux__) + +class MemoryMappingLayout { + public: + MemoryMappingLayout(); bool Next(uptr *start, uptr *end, uptr *offset, char filename[], uptr filename_size); void Reset(); @@ -28,9 +48,14 @@ class ProcessMaps { // address 'addr'. Returns true on success. bool GetObjectNameAndOffset(uptr addr, uptr *offset, char filename[], uptr filename_size); - ~ProcessMaps(); + // In some cases, e.g. when running under a sandbox on Linux, ASan is unable + // to obtain the memory mappings. It should fall back to pre-cached data + // instead of aborting. + static void CacheMemoryMappings(); + ~MemoryMappingLayout(); private: + void LoadFromCache(); // Default implementation of GetObjectNameAndOffset. // Quite slow, because it iterates through the whole process map for each // lookup. @@ -61,22 +86,27 @@ class ProcessMaps { return false; } -#if defined __linux__ - char *proc_self_maps_buff_; - uptr proc_self_maps_buff_mmaped_size_; - uptr proc_self_maps_buff_len_; +# if defined __linux__ + ProcSelfMapsBuff proc_self_maps_; char *current_; -#elif defined __APPLE__ + + // Static mappings cache. + static ProcSelfMapsBuff cached_proc_self_maps_; + static StaticSpinMutex cache_lock_; // protects cached_proc_self_maps_. +# elif defined __APPLE__ template<u32 kLCSegment, typename SegmentCommand> bool NextSegmentLoad(uptr *start, uptr *end, uptr *offset, char filename[], uptr filename_size); int current_image_; u32 current_magic_; + u32 current_filetype_; int current_load_cmd_count_; char *current_load_cmd_addr_; -#endif +# endif }; +#endif // _WIN32 + } // namespace __sanitizer #endif // SANITIZER_PROCMAPS_H diff --git a/lib/sanitizer_common/sanitizer_quarantine.h b/lib/sanitizer_common/sanitizer_quarantine.h new file mode 100644 index 000000000000..ec90d2d6871b --- /dev/null +++ b/lib/sanitizer_common/sanitizer_quarantine.h @@ -0,0 +1,172 @@ +//===-- sanitizer_quarantine.h ----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Memory quarantine for AddressSanitizer and potentially other tools. +// Quarantine caches some specified amount of memory in per-thread caches, +// then evicts to global FIFO queue. When the queue reaches specified threshold, +// oldest memory is recycled. +// +//===----------------------------------------------------------------------===// + +#ifndef SANITIZER_QUARANTINE_H +#define SANITIZER_QUARANTINE_H + +#include "sanitizer_internal_defs.h" +#include "sanitizer_mutex.h" +#include "sanitizer_list.h" + +namespace __sanitizer { + +template<typename Node> class QuarantineCache; + +struct QuarantineBatch { + static const uptr kSize = 1024; + QuarantineBatch *next; + uptr size; + uptr count; + void *batch[kSize]; +}; + +// The callback interface is: +// void Callback::Recycle(Node *ptr); +// void *cb.Allocate(uptr size); +// void cb.Deallocate(void *ptr); +template<typename Callback, typename Node> +class Quarantine { + public: + typedef QuarantineCache<Callback> Cache; + + explicit Quarantine(LinkerInitialized) + : cache_(LINKER_INITIALIZED) { + } + + void Init(uptr size, uptr cache_size) { + max_size_ = size; + min_size_ = size / 10 * 9; // 90% of max size. + max_cache_size_ = cache_size; + } + + void Put(Cache *c, Callback cb, Node *ptr, uptr size) { + c->Enqueue(cb, ptr, size); + if (c->Size() > max_cache_size_) + Drain(c, cb); + } + + void NOINLINE Drain(Cache *c, Callback cb) { + { + SpinMutexLock l(&cache_mutex_); + cache_.Transfer(c); + } + if (cache_.Size() > max_size_ && recycle_mutex_.TryLock()) + Recycle(cb); + } + + private: + // Read-only data. + char pad0_[kCacheLineSize]; + uptr max_size_; + uptr min_size_; + uptr max_cache_size_; + char pad1_[kCacheLineSize]; + SpinMutex cache_mutex_; + SpinMutex recycle_mutex_; + Cache cache_; + char pad2_[kCacheLineSize]; + + void NOINLINE Recycle(Callback cb) { + Cache tmp; + { + SpinMutexLock l(&cache_mutex_); + while (cache_.Size() > min_size_) { + QuarantineBatch *b = cache_.DequeueBatch(); + tmp.EnqueueBatch(b); + } + } + recycle_mutex_.Unlock(); + DoRecycle(&tmp, cb); + } + + void NOINLINE DoRecycle(Cache *c, Callback cb) { + while (QuarantineBatch *b = c->DequeueBatch()) { + const uptr kPrefetch = 16; + for (uptr i = 0; i < kPrefetch; i++) + PREFETCH(b->batch[i]); + for (uptr i = 0; i < b->count; i++) { + PREFETCH(b->batch[i + kPrefetch]); + cb.Recycle((Node*)b->batch[i]); + } + cb.Deallocate(b); + } + } +}; + +// Per-thread cache of memory blocks. +template<typename Callback> +class QuarantineCache { + public: + explicit QuarantineCache(LinkerInitialized) { + } + + QuarantineCache() + : size_() { + list_.clear(); + } + + uptr Size() const { + return atomic_load(&size_, memory_order_relaxed); + } + + void Enqueue(Callback cb, void *ptr, uptr size) { + if (list_.empty() || list_.back()->count == QuarantineBatch::kSize) + AllocBatch(cb); + QuarantineBatch *b = list_.back(); + b->batch[b->count++] = ptr; + b->size += size; + SizeAdd(size); + } + + void Transfer(QuarantineCache *c) { + list_.append_back(&c->list_); + SizeAdd(c->Size()); + atomic_store(&c->size_, 0, memory_order_relaxed); + } + + void EnqueueBatch(QuarantineBatch *b) { + list_.push_back(b); + SizeAdd(b->size); + } + + QuarantineBatch *DequeueBatch() { + if (list_.empty()) + return 0; + QuarantineBatch *b = list_.front(); + list_.pop_front(); + SizeAdd(-b->size); + return b; + } + + private: + IntrusiveList<QuarantineBatch> list_; + atomic_uintptr_t size_; + + void SizeAdd(uptr add) { + atomic_store(&size_, Size() + add, memory_order_relaxed); + } + + QuarantineBatch *NOINLINE AllocBatch(Callback cb) { + QuarantineBatch *b = (QuarantineBatch *)cb.Allocate(sizeof(*b)); + b->count = 0; + b->size = 0; + list_.push_back(b); + return b; + } +}; +} + +#endif // #ifndef SANITIZER_QUARANTINE_H diff --git a/lib/sanitizer_common/sanitizer_report_decorator.h b/lib/sanitizer_common/sanitizer_report_decorator.h new file mode 100644 index 000000000000..50a3ee572fdb --- /dev/null +++ b/lib/sanitizer_common/sanitizer_report_decorator.h @@ -0,0 +1,37 @@ +//===-- sanitizer_report_decorator.h ----------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Tags to decorate the sanitizer reports. +// Currently supported tags: +// * None. +// * ANSI color sequences. +// +//===----------------------------------------------------------------------===// + +#ifndef SANITIZER_ALLOCATOR_H +#define SANITIZER_ALLOCATOR_H + +namespace __sanitizer { +class AnsiColorDecorator { + public: + explicit AnsiColorDecorator(bool use_ansi_colors) : ansi_(use_ansi_colors) { } + const char *Black() { return ansi_ ? "\033[1m\033[30m" : ""; } + const char *Red() { return ansi_ ? "\033[1m\033[31m" : ""; } + const char *Green() { return ansi_ ? "\033[1m\033[32m" : ""; } + const char *Yellow() { return ansi_ ? "\033[1m\033[33m" : ""; } + const char *Blue() { return ansi_ ? "\033[1m\033[34m" : ""; } + const char *Magenta() { return ansi_ ? "\033[1m\033[35m" : ""; } + const char *Cyan() { return ansi_ ? "\033[1m\033[36m" : ""; } + const char *White() { return ansi_ ? "\033[1m\033[37m" : ""; } + const char *Default() { return ansi_ ? "\033[1m\033[0m" : ""; } + private: + bool ansi_; +}; +} // namespace __sanitizer +#endif // SANITIZER_ALLOCATOR_H diff --git a/lib/sanitizer_common/sanitizer_stackdepot.cc b/lib/sanitizer_common/sanitizer_stackdepot.cc new file mode 100644 index 000000000000..08e5238325e5 --- /dev/null +++ b/lib/sanitizer_common/sanitizer_stackdepot.cc @@ -0,0 +1,204 @@ +//===-- sanitizer_stackdepot.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 shared between AddressSanitizer and ThreadSanitizer +// run-time libraries. +//===----------------------------------------------------------------------===// + +#include "sanitizer_stackdepot.h" +#include "sanitizer_common.h" +#include "sanitizer_internal_defs.h" +#include "sanitizer_mutex.h" +#include "sanitizer_atomic.h" + +namespace __sanitizer { + +const int kTabSize = 1024 * 1024; // Hash table size. +const int kPartBits = 8; +const int kPartShift = sizeof(u32) * 8 - kPartBits - 1; +const int kPartCount = 1 << kPartBits; // Number of subparts in the table. +const int kPartSize = kTabSize / kPartCount; +const int kMaxId = 1 << kPartShift; + +struct StackDesc { + StackDesc *link; + u32 id; + u32 hash; + uptr size; + uptr stack[1]; // [size] +}; + +static struct { + StaticSpinMutex mtx; // Protects alloc of new blocks for region allocator. + atomic_uintptr_t region_pos; // Region allocator for StackDesc's. + atomic_uintptr_t region_end; + atomic_uintptr_t tab[kTabSize]; // Hash table of StackDesc's. + atomic_uint32_t seq[kPartCount]; // Unique id generators. +} depot; + +static StackDepotStats stats; + +StackDepotStats *StackDepotGetStats() { + return &stats; +} + +static u32 hash(const uptr *stack, uptr size) { + // murmur2 + const u32 m = 0x5bd1e995; + const u32 seed = 0x9747b28c; + const u32 r = 24; + u32 h = seed ^ (size * sizeof(uptr)); + for (uptr i = 0; i < size; i++) { + u32 k = stack[i]; + k *= m; + k ^= k >> r; + k *= m; + h *= m; + h ^= k; + } + h ^= h >> 13; + h *= m; + h ^= h >> 15; + return h; +} + +static StackDesc *tryallocDesc(uptr memsz) { + // Optimisic lock-free allocation, essentially try to bump the region ptr. + for (;;) { + uptr cmp = atomic_load(&depot.region_pos, memory_order_acquire); + uptr end = atomic_load(&depot.region_end, memory_order_acquire); + if (cmp == 0 || cmp + memsz > end) + return 0; + if (atomic_compare_exchange_weak( + &depot.region_pos, &cmp, cmp + memsz, + memory_order_acquire)) + return (StackDesc*)cmp; + } +} + +static StackDesc *allocDesc(uptr size) { + // First, try to allocate optimisitically. + uptr memsz = sizeof(StackDesc) + (size - 1) * sizeof(uptr); + StackDesc *s = tryallocDesc(memsz); + if (s) + return s; + // If failed, lock, retry and alloc new superblock. + SpinMutexLock l(&depot.mtx); + for (;;) { + s = tryallocDesc(memsz); + if (s) + return s; + atomic_store(&depot.region_pos, 0, memory_order_relaxed); + uptr allocsz = 64 * 1024; + if (allocsz < memsz) + allocsz = memsz; + uptr mem = (uptr)MmapOrDie(allocsz, "stack depot"); + stats.mapped += allocsz; + atomic_store(&depot.region_end, mem + allocsz, memory_order_release); + atomic_store(&depot.region_pos, mem, memory_order_release); + } +} + +static u32 find(StackDesc *s, const uptr *stack, uptr size, u32 hash) { + // Searches linked list s for the stack, returns its id. + for (; s; s = s->link) { + if (s->hash == hash && s->size == size) { + uptr i = 0; + for (; i < size; i++) { + if (stack[i] != s->stack[i]) + break; + } + if (i == size) + return s->id; + } + } + return 0; +} + +static StackDesc *lock(atomic_uintptr_t *p) { + // Uses the pointer lsb as mutex. + for (int i = 0;; i++) { + uptr cmp = atomic_load(p, memory_order_relaxed); + if ((cmp & 1) == 0 + && atomic_compare_exchange_weak(p, &cmp, cmp | 1, + memory_order_acquire)) + return (StackDesc*)cmp; + if (i < 10) + proc_yield(10); + else + internal_sched_yield(); + } +} + +static void unlock(atomic_uintptr_t *p, StackDesc *s) { + DCHECK_EQ((uptr)s & 1, 0); + atomic_store(p, (uptr)s, memory_order_release); +} + +u32 StackDepotPut(const uptr *stack, uptr size) { + if (stack == 0 || size == 0) + return 0; + uptr h = hash(stack, size); + atomic_uintptr_t *p = &depot.tab[h % kTabSize]; + uptr v = atomic_load(p, memory_order_consume); + StackDesc *s = (StackDesc*)(v & ~1); + // First, try to find the existing stack. + u32 id = find(s, stack, size, h); + if (id) + return id; + // If failed, lock, retry and insert new. + StackDesc *s2 = lock(p); + if (s2 != s) { + id = find(s2, stack, size, h); + if (id) { + unlock(p, s2); + return id; + } + } + uptr part = (h % kTabSize) / kPartSize; + id = atomic_fetch_add(&depot.seq[part], 1, memory_order_relaxed) + 1; + stats.n_uniq_ids++; + CHECK_LT(id, kMaxId); + id |= part << kPartShift; + CHECK_NE(id, 0); + CHECK_EQ(id & (1u << 31), 0); + s = allocDesc(size); + s->id = id; + s->hash = h; + s->size = size; + internal_memcpy(s->stack, stack, size * sizeof(uptr)); + s->link = s2; + unlock(p, s); + return id; +} + +const uptr *StackDepotGet(u32 id, uptr *size) { + if (id == 0) + return 0; + CHECK_EQ(id & (1u << 31), 0); + // High kPartBits contain part id, so we need to scan at most kPartSize lists. + uptr part = id >> kPartShift; + for (int i = 0; i != kPartSize; i++) { + uptr idx = part * kPartSize + i; + CHECK_LT(idx, kTabSize); + atomic_uintptr_t *p = &depot.tab[idx]; + uptr v = atomic_load(p, memory_order_consume); + StackDesc *s = (StackDesc*)(v & ~1); + for (; s; s = s->link) { + if (s->id == id) { + *size = s->size; + return s->stack; + } + } + } + *size = 0; + return 0; +} + +} // namespace __sanitizer diff --git a/lib/sanitizer_common/sanitizer_stackdepot.h b/lib/sanitizer_common/sanitizer_stackdepot.h new file mode 100644 index 000000000000..49e6669dd203 --- /dev/null +++ b/lib/sanitizer_common/sanitizer_stackdepot.h @@ -0,0 +1,36 @@ +//===-- sanitizer_stackdepot.h ----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is shared between AddressSanitizer and ThreadSanitizer +// run-time libraries. +//===----------------------------------------------------------------------===// +#ifndef SANITIZER_STACKDEPOT_H +#define SANITIZER_STACKDEPOT_H + +#include "sanitizer/common_interface_defs.h" + +namespace __sanitizer { + +// StackDepot efficiently stores huge amounts of stack traces. + +// Maps stack trace to an unique id. +u32 StackDepotPut(const uptr *stack, uptr size); +// Retrieves a stored stack trace by the id. +const uptr *StackDepotGet(u32 id, uptr *size); + +struct StackDepotStats { + uptr n_uniq_ids; + uptr mapped; +}; + +StackDepotStats *StackDepotGetStats(); + +} // namespace __sanitizer + +#endif // SANITIZER_STACKDEPOT_H diff --git a/lib/sanitizer_common/sanitizer_stacktrace.cc b/lib/sanitizer_common/sanitizer_stacktrace.cc new file mode 100644 index 000000000000..109a674e45b3 --- /dev/null +++ b/lib/sanitizer_common/sanitizer_stacktrace.cc @@ -0,0 +1,262 @@ +//===-- sanitizer_stacktrace.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 shared between AddressSanitizer and ThreadSanitizer +// run-time libraries. +//===----------------------------------------------------------------------===// + +#include "sanitizer_common.h" +#include "sanitizer_procmaps.h" +#include "sanitizer_stacktrace.h" +#include "sanitizer_symbolizer.h" + +namespace __sanitizer { +static const char *StripPathPrefix(const char *filepath, + const char *strip_file_prefix) { + if (filepath == internal_strstr(filepath, strip_file_prefix)) + return filepath + internal_strlen(strip_file_prefix); + return filepath; +} + +// ----------------------- StackTrace ----------------------------- {{{1 +uptr StackTrace::GetPreviousInstructionPc(uptr pc) { +#ifdef __arm__ + // Cancel Thumb bit. + pc = pc & (~1); +#endif +#if defined(__powerpc__) || defined(__powerpc64__) + // PCs are always 4 byte aligned. + return pc - 4; +#elif defined(__sparc__) + return pc - 8; +#else + return pc - 1; +#endif +} + +static void PrintStackFramePrefix(uptr frame_num, uptr pc) { + Printf(" #%zu 0x%zx", frame_num, pc); +} + +static void PrintSourceLocation(const char *file, int line, int column, + const char *strip_file_prefix) { + CHECK(file); + Printf(" %s", StripPathPrefix(file, strip_file_prefix)); + if (line > 0) { + Printf(":%d", line); + if (column > 0) + Printf(":%d", column); + } +} + +static void PrintModuleAndOffset(const char *module, uptr offset, + const char *strip_file_prefix) { + Printf(" (%s+0x%zx)", StripPathPrefix(module, strip_file_prefix), offset); +} + +void StackTrace::PrintStack(const uptr *addr, uptr size, + bool symbolize, const char *strip_file_prefix, + SymbolizeCallback symbolize_callback ) { + MemoryMappingLayout proc_maps; + InternalScopedBuffer<char> buff(GetPageSizeCached() * 2); + InternalScopedBuffer<AddressInfo> addr_frames(64); + uptr frame_num = 0; + for (uptr i = 0; i < size && addr[i]; i++) { + // PCs in stack traces are actually the return addresses, that is, + // addresses of the next instructions after the call. + uptr pc = GetPreviousInstructionPc(addr[i]); + uptr addr_frames_num = 0; // The number of stack frames for current + // instruction address. + if (symbolize_callback) { + if (symbolize_callback((void*)pc, buff.data(), buff.size())) { + addr_frames_num = 1; + PrintStackFramePrefix(frame_num, pc); + // We can't know anything about the string returned by external + // symbolizer, but if it starts with filename, try to strip path prefix + // from it. + Printf(" %s\n", StripPathPrefix(buff.data(), strip_file_prefix)); + frame_num++; + } + } + if (symbolize && addr_frames_num == 0) { + // Use our own (online) symbolizer, if necessary. + addr_frames_num = SymbolizeCode(pc, addr_frames.data(), + addr_frames.size()); + for (uptr j = 0; j < addr_frames_num; j++) { + AddressInfo &info = addr_frames[j]; + PrintStackFramePrefix(frame_num, pc); + if (info.function) { + Printf(" in %s", info.function); + } + if (info.file) { + PrintSourceLocation(info.file, info.line, info.column, + strip_file_prefix); + } else if (info.module) { + PrintModuleAndOffset(info.module, info.module_offset, + strip_file_prefix); + } + Printf("\n"); + info.Clear(); + frame_num++; + } + } + if (addr_frames_num == 0) { + // If online symbolization failed, try to output at least module and + // offset for instruction. + PrintStackFramePrefix(frame_num, pc); + uptr offset; + if (proc_maps.GetObjectNameAndOffset(pc, &offset, + buff.data(), buff.size())) { + PrintModuleAndOffset(buff.data(), offset, strip_file_prefix); + } + Printf("\n"); + frame_num++; + } + } +} + +uptr StackTrace::GetCurrentPc() { + return GET_CALLER_PC(); +} + +void StackTrace::FastUnwindStack(uptr pc, uptr bp, + uptr stack_top, uptr stack_bottom) { + CHECK(size == 0 && trace[0] == pc); + size = 1; + uhwptr *frame = (uhwptr *)bp; + uhwptr *prev_frame = frame; + while (frame >= prev_frame && + frame < (uhwptr *)stack_top - 2 && + frame > (uhwptr *)stack_bottom && + size < max_size) { + uhwptr pc1 = frame[1]; + if (pc1 != pc) { + trace[size++] = (uptr) pc1; + } + prev_frame = frame; + frame = (uhwptr *)frame[0]; + } +} + +void StackTrace::PopStackFrames(uptr count) { + CHECK(size >= count); + size -= count; + for (uptr i = 0; i < size; i++) { + trace[i] = trace[i + count]; + } +} + +// On 32-bits we don't compress stack traces. +// On 64-bits we compress stack traces: if a given pc differes slightly from +// the previous one, we record a 31-bit offset instead of the full pc. +SANITIZER_INTERFACE_ATTRIBUTE +uptr StackTrace::CompressStack(StackTrace *stack, u32 *compressed, uptr size) { +#if SANITIZER_WORDSIZE == 32 + // Don't compress, just copy. + uptr res = 0; + for (uptr i = 0; i < stack->size && i < size; i++) { + compressed[i] = stack->trace[i]; + res++; + } + if (stack->size < size) + compressed[stack->size] = 0; +#else // 64 bits, compress. + uptr prev_pc = 0; + const uptr kMaxOffset = (1ULL << 30) - 1; + uptr c_index = 0; + uptr res = 0; + for (uptr i = 0, n = stack->size; i < n; i++) { + uptr pc = stack->trace[i]; + if (!pc) break; + if ((s64)pc < 0) break; + // Printf("C pc[%zu] %zx\n", i, pc); + if (prev_pc - pc < kMaxOffset || pc - prev_pc < kMaxOffset) { + uptr offset = (s64)(pc - prev_pc); + offset |= (1U << 31); + if (c_index >= size) break; + // Printf("C co[%zu] offset %zx\n", i, offset); + compressed[c_index++] = offset; + } else { + uptr hi = pc >> 32; + uptr lo = (pc << 32) >> 32; + CHECK_EQ((hi & (1 << 31)), 0); + if (c_index + 1 >= size) break; + // Printf("C co[%zu] hi/lo: %zx %zx\n", c_index, hi, lo); + compressed[c_index++] = hi; + compressed[c_index++] = lo; + } + res++; + prev_pc = pc; + } + if (c_index < size) + compressed[c_index] = 0; + if (c_index + 1 < size) + compressed[c_index + 1] = 0; +#endif // SANITIZER_WORDSIZE + + // debug-only code +#if 0 + StackTrace check_stack; + UncompressStack(&check_stack, compressed, size); + if (res < check_stack.size) { + Printf("res %zu check_stack.size %zu; c_size %zu\n", res, + check_stack.size, size); + } + // |res| may be greater than check_stack.size, because + // UncompressStack(CompressStack(stack)) eliminates the 0x0 frames. + CHECK(res >= check_stack.size); + CHECK_EQ(0, REAL(memcmp)(check_stack.trace, stack->trace, + check_stack.size * sizeof(uptr))); +#endif + + return res; +} + +SANITIZER_INTERFACE_ATTRIBUTE +void StackTrace::UncompressStack(StackTrace *stack, + u32 *compressed, uptr size) { +#if SANITIZER_WORDSIZE == 32 + // Don't uncompress, just copy. + stack->size = 0; + for (uptr i = 0; i < size && i < kStackTraceMax; i++) { + if (!compressed[i]) break; + stack->size++; + stack->trace[i] = compressed[i]; + } +#else // 64 bits, uncompress + uptr prev_pc = 0; + stack->size = 0; + for (uptr i = 0; i < size && stack->size < kStackTraceMax; i++) { + u32 x = compressed[i]; + uptr pc = 0; + if (x & (1U << 31)) { + // Printf("U co[%zu] offset: %x\n", i, x); + // this is an offset + s32 offset = x; + offset = (offset << 1) >> 1; // remove the 31-byte and sign-extend. + pc = prev_pc + offset; + CHECK(pc); + } else { + // CHECK(i + 1 < size); + if (i + 1 >= size) break; + uptr hi = x; + uptr lo = compressed[i+1]; + // Printf("U co[%zu] hi/lo: %zx %zx\n", i, hi, lo); + i++; + pc = (hi << 32) | lo; + if (!pc) break; + } + // Printf("U pc[%zu] %zx\n", stack->size, pc); + stack->trace[stack->size++] = pc; + prev_pc = pc; + } +#endif // SANITIZER_WORDSIZE +} + +} // namespace __sanitizer diff --git a/lib/sanitizer_common/sanitizer_stacktrace.h b/lib/sanitizer_common/sanitizer_stacktrace.h new file mode 100644 index 000000000000..597d24fd067f --- /dev/null +++ b/lib/sanitizer_common/sanitizer_stacktrace.h @@ -0,0 +1,79 @@ +//===-- sanitizer_stacktrace.h ----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is shared between AddressSanitizer and ThreadSanitizer +// run-time libraries. +//===----------------------------------------------------------------------===// +#ifndef SANITIZER_STACKTRACE_H +#define SANITIZER_STACKTRACE_H + +#include "sanitizer_internal_defs.h" + +namespace __sanitizer { + +static const uptr kStackTraceMax = 256; + +struct StackTrace { + typedef bool (*SymbolizeCallback)(const void *pc, char *out_buffer, + int out_size); + uptr size; + uptr max_size; + uptr trace[kStackTraceMax]; + static void PrintStack(const uptr *addr, uptr size, + bool symbolize, const char *strip_file_prefix, + SymbolizeCallback symbolize_callback); + void CopyTo(uptr *dst, uptr dst_size) { + for (uptr i = 0; i < size && i < dst_size; i++) + dst[i] = trace[i]; + for (uptr i = size; i < dst_size; i++) + dst[i] = 0; + } + + void CopyFrom(uptr *src, uptr src_size) { + size = src_size; + if (size > kStackTraceMax) size = kStackTraceMax; + for (uptr i = 0; i < size; i++) { + trace[i] = src[i]; + } + } + + void FastUnwindStack(uptr pc, uptr bp, uptr stack_top, uptr stack_bottom); + void SlowUnwindStack(uptr pc, uptr max_depth); + + void PopStackFrames(uptr count); + + static uptr GetCurrentPc(); + static uptr GetPreviousInstructionPc(uptr pc); + + static uptr CompressStack(StackTrace *stack, + u32 *compressed, uptr size); + static void UncompressStack(StackTrace *stack, + u32 *compressed, uptr size); +}; + +} // namespace __sanitizer + +// Use this macro if you want to print stack trace with the caller +// of the current function in the top frame. +#define GET_CALLER_PC_BP_SP \ + uptr bp = GET_CURRENT_FRAME(); \ + uptr pc = GET_CALLER_PC(); \ + uptr local_stack; \ + uptr sp = (uptr)&local_stack + +// Use this macro if you want to print stack trace with the current +// function in the top frame. +#define GET_CURRENT_PC_BP_SP \ + uptr bp = GET_CURRENT_FRAME(); \ + uptr pc = StackTrace::GetCurrentPc(); \ + uptr local_stack; \ + uptr sp = (uptr)&local_stack + + +#endif // SANITIZER_STACKTRACE_H diff --git a/lib/sanitizer_common/sanitizer_symbolizer.cc b/lib/sanitizer_common/sanitizer_symbolizer.cc index 85eb0764f19c..a1d95ae0e0b2 100644 --- a/lib/sanitizer_common/sanitizer_symbolizer.cc +++ b/lib/sanitizer_common/sanitizer_symbolizer.cc @@ -7,9 +7,8 @@ // //===----------------------------------------------------------------------===// // -// This is a stub for LLVM-based symbolizer. // This file is shared between AddressSanitizer and ThreadSanitizer -// run-time libraries. See sanitizer.h for details. +// run-time libraries. See sanitizer_symbolizer.h for details. //===----------------------------------------------------------------------===// #include "sanitizer_common.h" @@ -19,18 +18,6 @@ namespace __sanitizer { -bool IsFullNameOfDWARFSection(const char *full_name, const char *short_name) { - // Skip "__DWARF," prefix. - if (0 == internal_strncmp(full_name, "__DWARF,", 8)) { - full_name += 8; - } - // Skip . and _ prefices. - while (*full_name == '.' || *full_name == '_') { - full_name++; - } - return 0 == internal_strcmp(full_name, short_name); -} - void AddressInfo::Clear() { InternalFree(module); InternalFree(function); @@ -38,28 +25,20 @@ void AddressInfo::Clear() { internal_memset(this, 0, sizeof(AddressInfo)); } -ModuleDIContext::ModuleDIContext(const char *module_name, uptr base_address) { +LoadedModule::LoadedModule(const char *module_name, uptr base_address) { full_name_ = internal_strdup(module_name); - short_name_ = internal_strrchr(module_name, '/'); - if (short_name_ == 0) { - short_name_ = full_name_; - } else { - short_name_++; - } base_address_ = base_address; n_ranges_ = 0; - mapped_addr_ = 0; - mapped_size_ = 0; } -void ModuleDIContext::addAddressRange(uptr beg, uptr end) { +void LoadedModule::addAddressRange(uptr beg, uptr end) { CHECK_LT(n_ranges_, kMaxNumberOfAddressRanges); ranges_[n_ranges_].beg = beg; ranges_[n_ranges_].end = end; n_ranges_++; } -bool ModuleDIContext::containsAddress(uptr address) const { +bool LoadedModule::containsAddress(uptr address) const { for (uptr i = 0; i < n_ranges_; i++) { if (ranges_[i].beg <= address && address < ranges_[i].end) return true; @@ -67,56 +46,256 @@ bool ModuleDIContext::containsAddress(uptr address) const { return false; } -void ModuleDIContext::getAddressInfo(AddressInfo *info) { - info->module = internal_strdup(full_name_); - info->module_offset = info->address - base_address_; - if (mapped_addr_ == 0) - CreateDIContext(); - // FIXME: Use the actual debug info context here. - info->function = 0; - info->file = 0; - info->line = 0; - info->column = 0; +// Extracts the prefix of "str" that consists of any characters not +// present in "delims" string, and copies this prefix to "result", allocating +// space for it. +// Returns a pointer to "str" after skipping extracted prefix and first +// delimiter char. +static const char *ExtractToken(const char *str, const char *delims, + char **result) { + uptr prefix_len = internal_strcspn(str, delims); + *result = (char*)InternalAlloc(prefix_len + 1); + internal_memcpy(*result, str, prefix_len); + (*result)[prefix_len] = '\0'; + const char *prefix_end = str + prefix_len; + if (*prefix_end != '\0') prefix_end++; + return prefix_end; +} + +// Same as ExtractToken, but converts extracted token to integer. +static const char *ExtractInt(const char *str, const char *delims, + int *result) { + char *buff; + const char *ret = ExtractToken(str, delims, &buff); + if (buff != 0) { + *result = (int)internal_atoll(buff); + } + InternalFree(buff); + return ret; } -void ModuleDIContext::CreateDIContext() { - mapped_addr_ = (uptr)MapFileToMemory(full_name_, &mapped_size_); - CHECK(mapped_addr_); - DWARFSection debug_info; - DWARFSection debug_abbrev; - DWARFSection debug_line; - DWARFSection debug_aranges; - DWARFSection debug_str; - FindDWARFSection(mapped_addr_, "debug_info", &debug_info); - FindDWARFSection(mapped_addr_, "debug_abbrev", &debug_abbrev); - FindDWARFSection(mapped_addr_, "debug_line", &debug_line); - FindDWARFSection(mapped_addr_, "debug_aranges", &debug_aranges); - FindDWARFSection(mapped_addr_, "debug_str", &debug_str); - // FIXME: Construct actual debug info context using mapped_addr, - // mapped_size and pointers to DWARF sections in memory. +static const char *ExtractUptr(const char *str, const char *delims, + uptr *result) { + char *buff; + const char *ret = ExtractToken(str, delims, &buff); + if (buff != 0) { + *result = (uptr)internal_atoll(buff); + } + InternalFree(buff); + return ret; } +// ExternalSymbolizer encapsulates communication between the tool and +// external symbolizer program, running in a different subprocess, +// For now we assume the following protocol: +// For each request of the form +// <module_name> <module_offset> +// passed to STDIN, external symbolizer prints to STDOUT response: +// <function_name> +// <file_name>:<line_number>:<column_number> +// <function_name> +// <file_name>:<line_number>:<column_number> +// ... +// <empty line> +class ExternalSymbolizer { + public: + ExternalSymbolizer(const char *path, int input_fd, int output_fd) + : path_(path), + input_fd_(input_fd), + output_fd_(output_fd), + times_restarted_(0) { + CHECK(path_); + CHECK_NE(input_fd_, kInvalidFd); + CHECK_NE(output_fd_, kInvalidFd); + } + + char *SendCommand(bool is_data, const char *module_name, uptr module_offset) { + CHECK(module_name); + internal_snprintf(buffer_, kBufferSize, "%s%s 0x%zx\n", + is_data ? "DATA " : "", module_name, module_offset); + if (!writeToSymbolizer(buffer_, internal_strlen(buffer_))) + return 0; + if (!readFromSymbolizer(buffer_, kBufferSize)) + return 0; + return buffer_; + } + + bool Restart() { + if (times_restarted_ >= kMaxTimesRestarted) return false; + times_restarted_++; + internal_close(input_fd_); + internal_close(output_fd_); + return StartSymbolizerSubprocess(path_, &input_fd_, &output_fd_); + } + + private: + bool readFromSymbolizer(char *buffer, uptr max_length) { + if (max_length == 0) + return true; + uptr read_len = 0; + while (true) { + uptr just_read = internal_read(input_fd_, buffer + read_len, + max_length - read_len); + // We can't read 0 bytes, as we don't expect external symbolizer to close + // its stdout. + if (just_read == 0 || just_read == (uptr)-1) { + Report("WARNING: Can't read from symbolizer at fd %d\n", input_fd_); + return false; + } + read_len += just_read; + // Empty line marks the end of symbolizer output. + if (read_len >= 2 && buffer[read_len - 1] == '\n' && + buffer[read_len - 2] == '\n') { + break; + } + } + return true; + } + + bool writeToSymbolizer(const char *buffer, uptr length) { + if (length == 0) + return true; + uptr write_len = internal_write(output_fd_, buffer, length); + if (write_len == 0 || write_len == (uptr)-1) { + Report("WARNING: Can't write to symbolizer at fd %d\n", output_fd_); + return false; + } + return true; + } + + const char *path_; + int input_fd_; + int output_fd_; + + static const uptr kBufferSize = 16 * 1024; + char buffer_[kBufferSize]; + + static const uptr kMaxTimesRestarted = 5; + uptr times_restarted_; +}; + +static LowLevelAllocator symbolizer_allocator; // Linker initialized. + class Symbolizer { public: uptr SymbolizeCode(uptr addr, AddressInfo *frames, uptr max_frames) { if (max_frames == 0) return 0; - AddressInfo *info = &frames[0]; - info->Clear(); - info->address = addr; - ModuleDIContext *module = FindModuleForAddress(addr); - if (module) { - module->getAddressInfo(info); + LoadedModule *module = FindModuleForAddress(addr); + if (module == 0) + return 0; + const char *module_name = module->full_name(); + uptr module_offset = addr - module->base_address(); + const char *str = SendCommand(false, module_name, module_offset); + if (str == 0) { + // External symbolizer was not initialized or failed. Fill only data + // about module name and offset. + AddressInfo *info = &frames[0]; + info->Clear(); + info->FillAddressAndModuleInfo(addr, module_name, module_offset); return 1; } - return 0; + uptr frame_id = 0; + for (frame_id = 0; frame_id < max_frames; frame_id++) { + AddressInfo *info = &frames[frame_id]; + char *function_name = 0; + str = ExtractToken(str, "\n", &function_name); + CHECK(function_name); + if (function_name[0] == '\0') { + // There are no more frames. + break; + } + info->Clear(); + info->FillAddressAndModuleInfo(addr, module_name, module_offset); + info->function = function_name; + // Parse <file>:<line>:<column> buffer. + char *file_line_info = 0; + str = ExtractToken(str, "\n", &file_line_info); + CHECK(file_line_info); + const char *line_info = ExtractToken(file_line_info, ":", &info->file); + line_info = ExtractInt(line_info, ":", &info->line); + line_info = ExtractInt(line_info, "", &info->column); + InternalFree(file_line_info); + + // Functions and filenames can be "??", in which case we write 0 + // to address info to mark that names are unknown. + if (0 == internal_strcmp(info->function, "??")) { + InternalFree(info->function); + info->function = 0; + } + if (0 == internal_strcmp(info->file, "??")) { + InternalFree(info->file); + info->file = 0; + } + } + if (frame_id == 0) { + // Make sure we return at least one frame. + AddressInfo *info = &frames[0]; + info->Clear(); + info->FillAddressAndModuleInfo(addr, module_name, module_offset); + frame_id = 1; + } + return frame_id; + } + + bool SymbolizeData(uptr addr, DataInfo *info) { + LoadedModule *module = FindModuleForAddress(addr); + if (module == 0) + return false; + const char *module_name = module->full_name(); + uptr module_offset = addr - module->base_address(); + internal_memset(info, 0, sizeof(*info)); + info->address = addr; + info->module = internal_strdup(module_name); + info->module_offset = module_offset; + const char *str = SendCommand(true, module_name, module_offset); + if (str == 0) + return true; + str = ExtractToken(str, "\n", &info->name); + str = ExtractUptr(str, " ", &info->start); + str = ExtractUptr(str, "\n", &info->size); + info->start += module->base_address(); + return true; + } + + bool InitializeExternalSymbolizer(const char *path_to_symbolizer) { + int input_fd, output_fd; + if (!StartSymbolizerSubprocess(path_to_symbolizer, &input_fd, &output_fd)) + return false; + void *mem = symbolizer_allocator.Allocate(sizeof(ExternalSymbolizer)); + external_symbolizer_ = new(mem) ExternalSymbolizer(path_to_symbolizer, + input_fd, output_fd); + return true; } private: - ModuleDIContext *FindModuleForAddress(uptr address) { + char *SendCommand(bool is_data, const char *module_name, uptr module_offset) { + if (external_symbolizer_ == 0) { + ReportExternalSymbolizerError( + "WARNING: Trying to symbolize code, but external " + "symbolizer is not initialized!\n"); + return 0; + } + for (;;) { + char *reply = external_symbolizer_->SendCommand(is_data, module_name, + module_offset); + if (reply) + return reply; + // Try to restart symbolizer subprocess. If we don't succeed, forget + // about it and don't try to use it later. + if (!external_symbolizer_->Restart()) { + ReportExternalSymbolizerError( + "WARNING: Failed to use and restart external symbolizer!\n"); + external_symbolizer_ = 0; + return 0; + } + } + } + + LoadedModule *FindModuleForAddress(uptr address) { if (modules_ == 0) { - modules_ = (ModuleDIContext*)InternalAlloc( - kMaxNumberOfModuleContexts * sizeof(ModuleDIContext)); + modules_ = (LoadedModule*)(symbolizer_allocator.Allocate( + kMaxNumberOfModuleContexts * sizeof(LoadedModule))); CHECK(modules_); n_modules_ = GetListOfModules(modules_, kMaxNumberOfModuleContexts); CHECK_GT(n_modules_, 0); @@ -129,10 +308,22 @@ class Symbolizer { } return 0; } - static const uptr kMaxNumberOfModuleContexts = 4096; - // Array of module debug info contexts is leaked. - ModuleDIContext *modules_; + void ReportExternalSymbolizerError(const char *msg) { + // Don't use atomics here for now, as SymbolizeCode can't be called + // from multiple threads anyway. + static bool reported; + if (!reported) { + Report(msg); + reported = true; + } + } + + // 16K loaded modules should be enough for everyone. + static const uptr kMaxNumberOfModuleContexts = 1 << 14; + LoadedModule *modules_; // Array of module descriptions is leaked. uptr n_modules_; + + ExternalSymbolizer *external_symbolizer_; // Leaked. }; static Symbolizer symbolizer; // Linker initialized. @@ -141,4 +332,12 @@ uptr SymbolizeCode(uptr address, AddressInfo *frames, uptr max_frames) { return symbolizer.SymbolizeCode(address, frames, max_frames); } +bool SymbolizeData(uptr address, DataInfo *info) { + return symbolizer.SymbolizeData(address, info); +} + +bool InitializeExternalSymbolizer(const char *path_to_symbolizer) { + return symbolizer.InitializeExternalSymbolizer(path_to_symbolizer); +} + } // namespace __sanitizer diff --git a/lib/sanitizer_common/sanitizer_symbolizer.h b/lib/sanitizer_common/sanitizer_symbolizer.h index c813e8088d7e..c26d621ea065 100644 --- a/lib/sanitizer_common/sanitizer_symbolizer.h +++ b/lib/sanitizer_common/sanitizer_symbolizer.h @@ -44,6 +44,22 @@ struct AddressInfo { } // Deletes all strings and sets all fields to zero. void Clear(); + + void FillAddressAndModuleInfo(uptr addr, const char *mod_name, + uptr mod_offset) { + address = addr; + module = internal_strdup(mod_name); + module_offset = mod_offset; + } +}; + +struct DataInfo { + uptr address; + char *module; + uptr module_offset; + char *name; + uptr start; + uptr size; }; // Fills at most "max_frames" elements of "frames" with descriptions @@ -51,49 +67,45 @@ struct AddressInfo { // of descriptions actually filled. // This function should NOT be called from two threads simultaneously. uptr SymbolizeCode(uptr address, AddressInfo *frames, uptr max_frames); +bool SymbolizeData(uptr address, DataInfo *info); -// Debug info routines -struct DWARFSection { - const char *data; - uptr size; - DWARFSection() { - data = 0; - size = 0; - } -}; -// Returns true on success. -bool FindDWARFSection(uptr object_file_addr, const char *section_name, - DWARFSection *section); -bool IsFullNameOfDWARFSection(const char *full_name, const char *short_name); +// Attempts to demangle the provided C++ mangled name. +const char *Demangle(const char *Name); -class ModuleDIContext { +// Starts external symbolizer program in a subprocess. Sanitizer communicates +// with external symbolizer via pipes. +bool InitializeExternalSymbolizer(const char *path_to_symbolizer); + +class LoadedModule { public: - ModuleDIContext(const char *module_name, uptr base_address); + LoadedModule(const char *module_name, uptr base_address); void addAddressRange(uptr beg, uptr end); bool containsAddress(uptr address) const; - void getAddressInfo(AddressInfo *info); const char *full_name() const { return full_name_; } + uptr base_address() const { return base_address_; } private: - void CreateDIContext(); - struct AddressRange { uptr beg; uptr end; }; char *full_name_; - char *short_name_; uptr base_address_; - static const uptr kMaxNumberOfAddressRanges = 8; + static const uptr kMaxNumberOfAddressRanges = 6; AddressRange ranges_[kMaxNumberOfAddressRanges]; uptr n_ranges_; - uptr mapped_addr_; - uptr mapped_size_; }; -// OS-dependent function that gets the linked list of all loaded modules. -uptr GetListOfModules(ModuleDIContext *modules, uptr max_modules); +// Creates external symbolizer connected via pipe, user should write +// to output_fd and read from input_fd. +bool StartSymbolizerSubprocess(const char *path_to_symbolizer, + int *input_fd, int *output_fd); + +// OS-dependent function that fills array with descriptions of at most +// "max_modules" currently loaded modules. Returns the number of +// initialized modules. +uptr GetListOfModules(LoadedModule *modules, uptr max_modules); } // namespace __sanitizer diff --git a/lib/sanitizer_common/sanitizer_symbolizer_itanium.cc b/lib/sanitizer_common/sanitizer_symbolizer_itanium.cc new file mode 100644 index 000000000000..438629492923 --- /dev/null +++ b/lib/sanitizer_common/sanitizer_symbolizer_itanium.cc @@ -0,0 +1,42 @@ +//===-- sanitizer_symbolizer_itanium.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 shared between the sanitizer run-time libraries. +// Itanium C++ ABI-specific implementation of symbolizer parts. +//===----------------------------------------------------------------------===// +#if defined(__APPLE__) || defined(__linux__) + +#include "sanitizer_symbolizer.h" + +#include <stdlib.h> + +// C++ demangling function, as required by Itanium C++ ABI. This is weak, +// because we do not require a C++ ABI library to be linked to a program +// using sanitizers; if it's not present, we'll just use the mangled name. +namespace __cxxabiv1 { + extern "C" char *__cxa_demangle(const char *mangled, char *buffer, + size_t *length, int *status) + SANITIZER_WEAK_ATTRIBUTE; +} + +const char *__sanitizer::Demangle(const char *MangledName) { + // FIXME: __cxa_demangle aggressively insists on allocating memory. + // There's not much we can do about that, short of providing our + // own demangler (libc++abi's implementation could be adapted so that + // it does not allocate). For now, we just call it anyway, and we leak + // the returned value. + if (__cxxabiv1::__cxa_demangle) + if (const char *Demangled = + __cxxabiv1::__cxa_demangle(MangledName, 0, 0, 0)) + return Demangled; + + return MangledName; +} + +#endif // __APPLE__ || __linux__ diff --git a/lib/sanitizer_common/sanitizer_symbolizer_linux.cc b/lib/sanitizer_common/sanitizer_symbolizer_linux.cc new file mode 100644 index 000000000000..4bd3dc8826ef --- /dev/null +++ b/lib/sanitizer_common/sanitizer_symbolizer_linux.cc @@ -0,0 +1,182 @@ +//===-- sanitizer_symbolizer_linux.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 shared between AddressSanitizer and ThreadSanitizer +// run-time libraries. +// Linux-specific implementation of symbolizer parts. +//===----------------------------------------------------------------------===// +#ifdef __linux__ +#include "sanitizer_common.h" +#include "sanitizer_internal_defs.h" +#include "sanitizer_libc.h" +#include "sanitizer_placement_new.h" +#include "sanitizer_symbolizer.h" + +#include <elf.h> +#include <errno.h> +#include <poll.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#if !defined(__ANDROID__) && !defined(ANDROID) +#include <link.h> +#endif + +namespace __sanitizer { + +static const int kSymbolizerStartupTimeMillis = 10; + +bool StartSymbolizerSubprocess(const char *path_to_symbolizer, + int *input_fd, int *output_fd) { + if (!FileExists(path_to_symbolizer)) { + Report("WARNING: invalid path to external symbolizer!\n"); + return false; + } + + int *infd = NULL; + int *outfd = NULL; + // The client program may close its stdin and/or stdout and/or stderr + // thus allowing socketpair to reuse file descriptors 0, 1 or 2. + // In this case the communication between the forked processes may be + // broken if either the parent or the child tries to close or duplicate + // these descriptors. The loop below produces two pairs of file + // descriptors, each greater than 2 (stderr). + int sock_pair[5][2]; + for (int i = 0; i < 5; i++) { + if (pipe(sock_pair[i]) == -1) { + for (int j = 0; j < i; j++) { + internal_close(sock_pair[j][0]); + internal_close(sock_pair[j][1]); + } + Report("WARNING: Can't create a socket pair to start " + "external symbolizer (errno: %d)\n", errno); + return false; + } else if (sock_pair[i][0] > 2 && sock_pair[i][1] > 2) { + if (infd == NULL) { + infd = sock_pair[i]; + } else { + outfd = sock_pair[i]; + for (int j = 0; j < i; j++) { + if (sock_pair[j] == infd) continue; + internal_close(sock_pair[j][0]); + internal_close(sock_pair[j][1]); + } + break; + } + } + } + CHECK(infd); + CHECK(outfd); + + int pid = fork(); + if (pid == -1) { + // Fork() failed. + internal_close(infd[0]); + internal_close(infd[1]); + internal_close(outfd[0]); + internal_close(outfd[1]); + Report("WARNING: failed to fork external symbolizer " + " (errno: %d)\n", errno); + return false; + } else if (pid == 0) { + // Child subprocess. + internal_close(STDOUT_FILENO); + internal_close(STDIN_FILENO); + internal_dup2(outfd[0], STDIN_FILENO); + internal_dup2(infd[1], STDOUT_FILENO); + internal_close(outfd[0]); + internal_close(outfd[1]); + internal_close(infd[0]); + internal_close(infd[1]); + for (int fd = getdtablesize(); fd > 2; fd--) + internal_close(fd); + execl(path_to_symbolizer, path_to_symbolizer, (char*)0); + Exit(1); + } + + // Continue execution in parent process. + internal_close(outfd[0]); + internal_close(infd[1]); + *input_fd = infd[0]; + *output_fd = outfd[1]; + + // Check that symbolizer subprocess started successfully. + int pid_status; + SleepForMillis(kSymbolizerStartupTimeMillis); + int exited_pid = waitpid(pid, &pid_status, WNOHANG); + if (exited_pid != 0) { + // Either waitpid failed, or child has already exited. + Report("WARNING: external symbolizer didn't start up correctly!\n"); + return false; + } + + return true; +} + +#if defined(__ANDROID__) || defined(ANDROID) +uptr GetListOfModules(LoadedModule *modules, uptr max_modules) { + UNIMPLEMENTED(); +} +#else // ANDROID +typedef ElfW(Phdr) Elf_Phdr; + +struct DlIteratePhdrData { + LoadedModule *modules; + uptr current_n; + uptr max_n; +}; + +static const uptr kMaxPathLength = 512; + +static int dl_iterate_phdr_cb(dl_phdr_info *info, size_t size, void *arg) { + DlIteratePhdrData *data = (DlIteratePhdrData*)arg; + if (data->current_n == data->max_n) + return 0; + InternalScopedBuffer<char> module_name(kMaxPathLength); + module_name.data()[0] = '\0'; + if (data->current_n == 0) { + // First module is the binary itself. + uptr module_name_len = internal_readlink( + "/proc/self/exe", module_name.data(), module_name.size()); + CHECK_NE(module_name_len, (uptr)-1); + CHECK_LT(module_name_len, module_name.size()); + module_name[module_name_len] = '\0'; + } else if (info->dlpi_name) { + internal_strncpy(module_name.data(), info->dlpi_name, module_name.size()); + } + if (module_name.data()[0] == '\0') + return 0; + void *mem = &data->modules[data->current_n]; + LoadedModule *cur_module = new(mem) LoadedModule(module_name.data(), + info->dlpi_addr); + data->current_n++; + for (int i = 0; i < info->dlpi_phnum; i++) { + const Elf_Phdr *phdr = &info->dlpi_phdr[i]; + if (phdr->p_type == PT_LOAD) { + uptr cur_beg = info->dlpi_addr + phdr->p_vaddr; + uptr cur_end = cur_beg + phdr->p_memsz; + cur_module->addAddressRange(cur_beg, cur_end); + } + } + return 0; +} + +uptr GetListOfModules(LoadedModule *modules, uptr max_modules) { + CHECK(modules); + DlIteratePhdrData data = {modules, 0, max_modules}; + dl_iterate_phdr(dl_iterate_phdr_cb, &data); + return data.current_n; +} +#endif // ANDROID + +} // namespace __sanitizer + +#endif // __linux__ diff --git a/lib/sanitizer_common/sanitizer_symbolizer_mac.cc b/lib/sanitizer_common/sanitizer_symbolizer_mac.cc new file mode 100644 index 000000000000..23993607e77b --- /dev/null +++ b/lib/sanitizer_common/sanitizer_symbolizer_mac.cc @@ -0,0 +1,31 @@ +//===-- sanitizer_symbolizer_mac.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 shared between AddressSanitizer and ThreadSanitizer +// run-time libraries. +// Mac-specific implementation of symbolizer parts. +//===----------------------------------------------------------------------===// +#ifdef __APPLE__ +#include "sanitizer_internal_defs.h" +#include "sanitizer_symbolizer.h" + +namespace __sanitizer { + +bool StartSymbolizerSubprocess(const char *path_to_symbolizer, + int *input_fd, int *output_fd) { + UNIMPLEMENTED(); +} + +uptr GetListOfModules(LoadedModule *modules, uptr max_modules) { + UNIMPLEMENTED(); +} + +} // namespace __sanitizer + +#endif // __APPLE__ diff --git a/lib/sanitizer_common/sanitizer_symbolizer_win.cc b/lib/sanitizer_common/sanitizer_symbolizer_win.cc new file mode 100644 index 000000000000..f1b6a02a6f9a --- /dev/null +++ b/lib/sanitizer_common/sanitizer_symbolizer_win.cc @@ -0,0 +1,37 @@ +//===-- sanitizer_symbolizer_win.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 shared between AddressSanitizer and ThreadSanitizer +// run-time libraries. +// Windows-specific implementation of symbolizer parts. +//===----------------------------------------------------------------------===// +#ifdef _WIN32 +#include <windows.h> + +#include "sanitizer_internal_defs.h" +#include "sanitizer_symbolizer.h" + +namespace __sanitizer { + +bool StartSymbolizerSubprocess(const char *path_to_symbolizer, + int *input_fd, int *output_fd) { + UNIMPLEMENTED(); +} + +uptr GetListOfModules(LoadedModule *modules, uptr max_modules) { + UNIMPLEMENTED(); +}; + +const char *Demangle(const char *MangledName) { + return MangledName; +} + +} // namespace __sanitizer + +#endif // _WIN32 diff --git a/lib/sanitizer_common/sanitizer_win.cc b/lib/sanitizer_common/sanitizer_win.cc index c68a1fee4068..2ae37af8847c 100644 --- a/lib/sanitizer_common/sanitizer_win.cc +++ b/lib/sanitizer_common/sanitizer_win.cc @@ -12,15 +12,32 @@ // sanitizer_libc.h. //===----------------------------------------------------------------------===// #ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#define NOGDI +#include <stdlib.h> +#include <io.h> #include <windows.h> #include "sanitizer_common.h" #include "sanitizer_libc.h" -#include "sanitizer_symbolizer.h" +#include "sanitizer_placement_new.h" +#include "sanitizer_mutex.h" namespace __sanitizer { // --------------------- sanitizer_common.h +uptr GetPageSize() { + return 1U << 14; // FIXME: is this configurable? +} + +uptr GetMmapGranularity() { + return 1U << 16; // FIXME: is this configurable? +} + +bool FileExists(const char *filename) { + UNIMPLEMENTED(); +} + int GetPid() { return GetProcessId(GetCurrentProcess()); } @@ -42,7 +59,6 @@ void GetThreadStackTopAndBottom(bool at_initialization, uptr *stack_top, *stack_bottom = (uptr)mbi.AllocationBase; } - void *MmapOrDie(uptr size, const char *mem_type) { void *rv = VirtualAlloc(0, size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); if (rv == 0) { @@ -62,8 +78,18 @@ void UnmapOrDie(void *addr, uptr size) { } void *MmapFixedNoReserve(uptr fixed_addr, uptr size) { - return VirtualAlloc((LPVOID)fixed_addr, size, - MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + // FIXME: is this really "NoReserve"? On Win32 this does not matter much, + // but on Win64 it does. + void *p = VirtualAlloc((LPVOID)fixed_addr, size, + MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + if (p == 0) + Report("ERROR: Failed to allocate 0x%zx (%zd) bytes at %p (%d)\n", + size, size, fixed_addr, GetLastError()); + return p; +} + +void *MmapFixedOrDie(uptr fixed_addr, uptr size) { + return MmapFixedNoReserve(fixed_addr, size); } void *Mprotect(uptr fixed_addr, uptr size) { @@ -98,7 +124,6 @@ const char *GetEnv(const char *name) { const char *GetPwd() { UNIMPLEMENTED(); - return 0; } void DumpProcessMap() { @@ -109,6 +134,22 @@ void DisableCoreDumper() { UNIMPLEMENTED(); } +void ReExec() { + UNIMPLEMENTED(); +} + +void PrepareForSandboxing() { + // Nothing here for now. +} + +bool StackSizeIsUnlimited() { + UNIMPLEMENTED(); +} + +void SetStackSizeLimitInBytes(uptr limit) { + UNIMPLEMENTED(); +} + void SleepForSeconds(int seconds) { Sleep(seconds * 1000); } @@ -126,50 +167,40 @@ void Abort() { _exit(-1); // abort is not NORETURN on Windows. } +#ifndef SANITIZER_GO int Atexit(void (*function)(void)) { return atexit(function); } - -// ------------------ sanitizer_symbolizer.h -bool FindDWARFSection(uptr object_file_addr, const char *section_name, - DWARFSection *section) { - UNIMPLEMENTED(); - return false; -} - -uptr GetListOfModules(ModuleDIContext *modules, uptr max_modules) { - UNIMPLEMENTED(); -}; +#endif // ------------------ sanitizer_libc.h void *internal_mmap(void *addr, uptr length, int prot, int flags, int fd, u64 offset) { UNIMPLEMENTED(); - return 0; } int internal_munmap(void *addr, uptr length) { UNIMPLEMENTED(); - return 0; } int internal_close(fd_t fd) { UNIMPLEMENTED(); - return 0; +} + +int internal_isatty(fd_t fd) { + return _isatty(fd); } fd_t internal_open(const char *filename, bool write) { UNIMPLEMENTED(); - return 0; } uptr internal_read(fd_t fd, void *buf, uptr count) { UNIMPLEMENTED(); - return 0; } uptr internal_write(fd_t fd, const void *buf, uptr count) { - if (fd != 2) + if (fd != kStderrFd) UNIMPLEMENTED(); HANDLE err = GetStdHandle(STD_ERROR_HANDLE); if (err == 0) @@ -182,19 +213,57 @@ uptr internal_write(fd_t fd, const void *buf, uptr count) { uptr internal_filesize(fd_t fd) { UNIMPLEMENTED(); - return 0; } int internal_dup2(int oldfd, int newfd) { UNIMPLEMENTED(); - return 0; } -int internal_sched_yield() { +uptr internal_readlink(const char *path, char *buf, uptr bufsize) { UNIMPLEMENTED(); +} + +int internal_sched_yield() { + Sleep(0); return 0; } +// ---------------------- BlockingMutex ---------------- {{{1 +enum LockState { + LOCK_UNINITIALIZED = 0, + LOCK_READY = -1, +}; + +BlockingMutex::BlockingMutex(LinkerInitialized li) { + // FIXME: see comments in BlockingMutex::Lock() for the details. + CHECK(li == LINKER_INITIALIZED || owner_ == LOCK_UNINITIALIZED); + + CHECK(sizeof(CRITICAL_SECTION) <= sizeof(opaque_storage_)); + InitializeCriticalSection((LPCRITICAL_SECTION)opaque_storage_); + owner_ = LOCK_READY; +} + +void BlockingMutex::Lock() { + if (owner_ == LOCK_UNINITIALIZED) { + // FIXME: hm, global BlockingMutex objects are not initialized?!? + // This might be a side effect of the clang+cl+link Frankenbuild... + new(this) BlockingMutex((LinkerInitialized)(LINKER_INITIALIZED + 1)); + + // FIXME: If it turns out the linker doesn't invoke our + // constructors, we should probably manually Lock/Unlock all the global + // locks while we're starting in one thread to avoid double-init races. + } + EnterCriticalSection((LPCRITICAL_SECTION)opaque_storage_); + CHECK(owner_ == LOCK_READY); + owner_ = GetThreadSelf(); +} + +void BlockingMutex::Unlock() { + CHECK(owner_ == GetThreadSelf()); + owner_ = LOCK_READY; + LeaveCriticalSection((LPCRITICAL_SECTION)opaque_storage_); +} + } // namespace __sanitizer #endif // _WIN32 diff --git a/lib/sanitizer_common/scripts/check_lint.sh b/lib/sanitizer_common/scripts/check_lint.sh new file mode 100755 index 000000000000..e65794df0ce7 --- /dev/null +++ b/lib/sanitizer_common/scripts/check_lint.sh @@ -0,0 +1,82 @@ +#!/bin/bash + +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +# Guess path to LLVM_CHECKOUT if not provided +if [ "${LLVM_CHECKOUT}" == "" ]; then + LLVM_CHECKOUT="${SCRIPT_DIR}/../../../../../" + echo "LLVM Checkout: ${LLVM_CHECKOUT}" +fi + +# Cpplint setup +cd ${SCRIPT_DIR} +if [ ! -d cpplint ]; then + svn co -r83 http://google-styleguide.googlecode.com/svn/trunk/cpplint cpplint +fi +CPPLINT=${SCRIPT_DIR}/cpplint/cpplint.py + +# Filters +# TODO: remove some of these filters +ASAN_RTL_LINT_FILTER=-readability/casting,-readability/check,-build/include,-build/header_guard,-build/class,-legal/copyright,-build/namespaces +ASAN_TEST_LINT_FILTER=-readability/casting,-build/include,-legal/copyright,-whitespace/newline,-runtime/sizeof,-runtime/int,-runtime/printf,-build/header_guard +ASAN_LIT_TEST_LINT_FILTER=${ASAN_TEST_LINT_FILTER},-whitespace/line_length +TSAN_RTL_LINT_FILTER=-legal/copyright,-build/include,-readability/casting,-build/header_guard,-build/namespaces +TSAN_TEST_LINT_FILTER=${TSAN_RTL_LINT_FILTER},-runtime/threadsafe_fn,-runtime/int +TSAN_LIT_TEST_LINT_FILTER=${TSAN_TEST_LINT_FILTER},-whitespace/line_length +MSAN_RTL_LINT_FILTER=-legal/copyright,-build/include,-readability/casting,-build/header_guard,-build/namespaces +TSAN_RTL_INC_LINT_FILTER=${TSAN_TEST_LINT_FILTER},-runtime/sizeof + +cd ${LLVM_CHECKOUT} + +# LLVM Instrumentation +LLVM_INSTRUMENTATION=lib/Transforms/Instrumentation +LLVM_LINT_FILTER=-,+whitespace +${CPPLINT} --filter=${LLVM_LINT_FILTER} ${LLVM_INSTRUMENTATION}/*Sanitizer.cpp \ + ${LLVM_INSTRUMENTATION}/BlackList.* + +COMPILER_RT=projects/compiler-rt + +# Headers +SANITIZER_INCLUDES=${COMPILER_RT}/include/sanitizer +${CPPLINT} --filter=${TSAN_RTL_LINT_FILTER} ${SANITIZER_INCLUDES}/*.h + +# Sanitizer_common +COMMON_RTL=${COMPILER_RT}/lib/sanitizer_common +${CPPLINT} --filter=${ASAN_RTL_LINT_FILTER} ${COMMON_RTL}/*.{cc,h} +${CPPLINT} --filter=${TSAN_RTL_LINT_FILTER} ${COMMON_RTL}/tests/*.cc + +# Interception +INTERCEPTION=${COMPILER_RT}/lib/interception +${CPPLINT} --filter=${ASAN_RTL_LINT_FILTER} ${INTERCEPTION}/*.{cc,h} + +# ASan +ASAN_RTL=${COMPILER_RT}/lib/asan +${CPPLINT} --filter=${ASAN_RTL_LINT_FILTER} ${ASAN_RTL}/*.{cc,h} +${CPPLINT} --filter=${ASAN_TEST_LINT_FILTER} ${ASAN_RTL}/tests/*.{cc,h} +${CPPLINT} --filter=${ASAN_LIT_TEST_LINT_FILTER} ${ASAN_RTL}/lit_tests/*.cc \ + ${ASAN_RTL}/lit_tests/*/*.cc \ + +# TSan +TSAN_RTL=${COMPILER_RT}/lib/tsan +${CPPLINT} --filter=${TSAN_RTL_LINT_FILTER} ${TSAN_RTL}/rtl/*.{cc,h} +${CPPLINT} --filter=${TSAN_TEST_LINT_FILTER} ${TSAN_RTL}/tests/rtl/*.{cc,h} \ + ${TSAN_RTL}/tests/unit/*.cc +${CPPLINT} --filter=${TSAN_LIT_TEST_LINT_FILTER} ${TSAN_RTL}/lit_tests/*.cc + +# MSan +MSAN_RTL=${COMPILER_RT}/lib/msan +${CPPLINT} --filter=${MSAN_RTL_LINT_FILTER} ${MSAN_RTL}/*.{cc,h} + +set +e + +# Misc files +FILES=${COMMON_RTL}/*.inc +for FILE in $FILES; do + TMPFILE=$(mktemp -u ${FILE}.XXXXX).cc + echo "Checking $FILE" + cp -f $FILE $TMPFILE && \ + ${CPPLINT} --filter=${TSAN_RTL_INC_LINT_FILTER} $TMPFILE + rm $TMPFILE +done diff --git a/lib/sanitizer_common/tests/CMakeLists.txt b/lib/sanitizer_common/tests/CMakeLists.txt new file mode 100644 index 000000000000..f83a89cbe37c --- /dev/null +++ b/lib/sanitizer_common/tests/CMakeLists.txt @@ -0,0 +1,138 @@ +include(CompilerRTCompile) + +set(SANITIZER_UNITTESTS + sanitizer_allocator_test.cc + sanitizer_common_test.cc + sanitizer_flags_test.cc + sanitizer_libc_test.cc + sanitizer_list_test.cc + sanitizer_mutex_test.cc + sanitizer_printf_test.cc + sanitizer_scanf_interceptor_test.cc + sanitizer_stackdepot_test.cc + sanitizer_test_main.cc + ) + +set(SANITIZER_TEST_HEADERS) +foreach(header ${SANITIZER_HEADERS}) + list(APPEND SANITIZER_TEST_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/../${header}) +endforeach() + +include_directories(..) +include_directories(../..) + +# Adds static library which contains sanitizer_common object file +# (universal binary on Mac and arch-specific object files on Linux). +macro(add_sanitizer_common_lib library) + add_library(${library} STATIC ${ARGN}) + set_target_properties(${library} PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) +endmacro() + +function(get_sanitizer_common_lib_for_arch arch lib lib_name) + if(APPLE) + set(tgt_name "RTSanitizerCommon.test.osx") + else() + set(tgt_name "RTSanitizerCommon.test.${arch}") + endif() + set(${lib} "${tgt_name}" PARENT_SCOPE) + set(${lib_name} "lib${tgt_name}.a" PARENT_SCOPE) +endfunction() + +# Sanitizer_common unit tests testsuite. +add_custom_target(SanitizerUnitTests) +set_target_properties(SanitizerUnitTests PROPERTIES + FOLDER "Sanitizer unittests") + +# Adds sanitizer tests for architecture. +macro(add_sanitizer_tests_for_arch arch) + get_target_flags_for_arch(${arch} TARGET_FLAGS) + set(SANITIZER_TEST_SOURCES ${SANITIZER_UNITTESTS} + ${COMPILER_RT_GTEST_SOURCE}) + set(SANITIZER_TEST_CFLAGS ${COMPILER_RT_GTEST_INCLUDE_CFLAGS} + -I${COMPILER_RT_SOURCE_DIR}/include + -I${COMPILER_RT_SOURCE_DIR}/lib + -I${COMPILER_RT_SOURCE_DIR}/lib/sanitizer_common + -O2 -g ${TARGET_FLAGS}) + set(SANITIZER_TEST_LINK_FLAGS -lstdc++ -lpthread ${TARGET_FLAGS}) + set(SANITIZER_TEST_OBJECTS) + foreach(source ${SANITIZER_TEST_SOURCES}) + get_filename_component(basename ${source} NAME) + set(output_obj "${basename}.${arch}.o") + clang_compile(${output_obj} ${source} + CFLAGS ${SANITIZER_TEST_CFLAGS} + DEPS gtest ${SANITIZER_RUNTIME_LIBRARIES} + ${SANITIZER_TEST_HEADERS}) + list(APPEND SANITIZER_TEST_OBJECTS ${output_obj}) + endforeach() + get_sanitizer_common_lib_for_arch(${arch} SANITIZER_COMMON_LIB + SANITIZER_COMMON_LIB_NAME) + # Add unittest target. + set(SANITIZER_TEST_NAME "Sanitizer-${arch}-Test") + add_compiler_rt_test(SanitizerUnitTests ${SANITIZER_TEST_NAME} + OBJECTS ${SANITIZER_TEST_OBJECTS} + ${SANITIZER_COMMON_LIB_NAME} + DEPS ${SANITIZER_TEST_OBJECTS} ${SANITIZER_COMMON_LIB} + LINK_FLAGS ${SANITIZER_TEST_LINK_FLAGS}) +endmacro() + +if(COMPILER_RT_CAN_EXECUTE_TESTS) + # We use just-built clang to build sanitizer_common unittests, so we must + # be sure that produced binaries would work. + if(APPLE) + add_sanitizer_common_lib("RTSanitizerCommon.test.osx" + $<TARGET_OBJECTS:RTSanitizerCommon.osx>) + else() + if(CAN_TARGET_x86_64) + add_sanitizer_common_lib("RTSanitizerCommon.test.x86_64" + $<TARGET_OBJECTS:RTSanitizerCommon.x86_64>) + endif() + if(CAN_TARGET_i386) + add_sanitizer_common_lib("RTSanitizerCommon.test.i386" + $<TARGET_OBJECTS:RTSanitizerCommon.i386>) + endif() + endif() + if(CAN_TARGET_x86_64) + add_sanitizer_tests_for_arch(x86_64) + endif() + if(CAN_TARGET_i386) + add_sanitizer_tests_for_arch(i386) + endif() + + # Run unittests as a part of lit testsuite. + configure_lit_site_cfg( + ${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.in + ${CMAKE_CURRENT_BINARY_DIR}/lit.site.cfg + ) + + add_lit_testsuite(check-sanitizer "Running sanitizer library unittests" + ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS SanitizerUnitTests + ) + set_target_properties(check-sanitizer PROPERTIES FOLDER "Sanitizer unittests") +endif() + +if(ANDROID) + # We assume that unit tests on Android are built in a build + # tree with fresh Clang as a host compiler. + add_executable(SanitizerTest + ${SANITIZER_UNITTESTS} + ${COMPILER_RT_GTEST_SOURCE} + $<TARGET_OBJECTS:RTSanitizerCommon.arm.android> + ) + set_target_compile_flags(SanitizerTest + ${SANITIZER_COMMON_CFLAGS} + ${COMPILER_RT_GTEST_INCLUDE_CFLAGS} + -I${COMPILER_RT_SOURCE_DIR}/include + -I${COMPILER_RT_SOURCE_DIR}/lib + -I${COMPILER_RT_SOURCE_DIR}/lib/sanitizer_common + -O2 -g + ) + # Setup correct output directory and link flags. + get_unittest_directory(OUTPUT_DIR) + set_target_properties(SanitizerTest PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${OUTPUT_DIR}) + set_target_link_flags(SanitizerTest ${SANITIZER_TEST_LINK_FLAGS}) + # Add unit test to test suite. + add_dependencies(SanitizerUnitTests SanitizerTest) +endif() diff --git a/lib/sanitizer_common/tests/lit.cfg b/lib/sanitizer_common/tests/lit.cfg new file mode 100644 index 000000000000..d774753985ac --- /dev/null +++ b/lib/sanitizer_common/tests/lit.cfg @@ -0,0 +1,29 @@ +# -*- Python -*- + +import os + +def get_required_attr(config, attr_name): + attr_value = getattr(config, attr_name, None) + if not attr_value: + lit.fatal("No attribute %r in test configuration! You may need to run " + "tests from your build directory or add this attribute " + "to lit.site.cfg " % attr_name) + return attr_value + +# Setup attributes common for all compiler-rt projects. +llvm_src_root = get_required_attr(config, 'llvm_src_root') +compiler_rt_lit_unit_cfg = os.path.join(llvm_src_root, "projects", + "compiler-rt", "lib", + "lit.common.unit.cfg") +lit.load_config(config, compiler_rt_lit_unit_cfg) + +# Setup config name. +config.name = 'SanitizerCommon-Unit' + +# Setup test source and exec root. For unit tests, we define +# it as build directory with sanitizer_common unit tests. +llvm_obj_root = get_required_attr(config, "llvm_obj_root") +config.test_exec_root = os.path.join(llvm_obj_root, "projects", + "compiler-rt", "lib", + "sanitizer_common", "tests") +config.test_source_root = config.test_exec_root diff --git a/lib/sanitizer_common/tests/lit.site.cfg.in b/lib/sanitizer_common/tests/lit.site.cfg.in new file mode 100644 index 000000000000..bb9a28d6a6cb --- /dev/null +++ b/lib/sanitizer_common/tests/lit.site.cfg.in @@ -0,0 +1,9 @@ +## Autogenerated by LLVM/Clang configuration. +# Do not edit! + +config.build_type = "@CMAKE_BUILD_TYPE@" +config.llvm_obj_root = "@LLVM_BINARY_DIR@" +config.llvm_src_root = "@LLVM_SOURCE_DIR@" + +# Let the main config do the real work. +lit.load_config(config, "@CMAKE_CURRENT_SOURCE_DIR@/lit.cfg") diff --git a/lib/sanitizer_common/tests/sanitizer_allocator64_test.cc b/lib/sanitizer_common/tests/sanitizer_allocator64_test.cc deleted file mode 100644 index 1410f26ce84f..000000000000 --- a/lib/sanitizer_common/tests/sanitizer_allocator64_test.cc +++ /dev/null @@ -1,257 +0,0 @@ -//===-- sanitizer_allocator64_test.cc -------------------------------------===// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// -// Tests for sanitizer_allocator64.h. -//===----------------------------------------------------------------------===// -#include "sanitizer_common/sanitizer_allocator64.h" -#include "gtest/gtest.h" - -#include <algorithm> -#include <vector> - -static const uptr kAllocatorSpace = 0x600000000000ULL; -static const uptr kAllocatorSize = 0x10000000000; // 1T. - -typedef DefaultSizeClassMap SCMap; -typedef - SizeClassAllocator64<kAllocatorSpace, kAllocatorSize, 16, SCMap> Allocator; -typedef SizeClassAllocatorLocalCache<Allocator::kNumClasses, Allocator> - AllocatorCache; - -TEST(SanitizerCommon, DefaultSizeClassMap) { -#if 0 - for (uptr i = 0; i < SCMap::kNumClasses; i++) { - // printf("% 3ld: % 5ld (%4lx); ", i, SCMap::Size(i), SCMap::Size(i)); - printf("c%ld => %ld ", i, SCMap::Size(i)); - if ((i % 8) == 7) - printf("\n"); - } - printf("\n"); -#endif - - for (uptr c = 0; c < SCMap::kNumClasses; c++) { - uptr s = SCMap::Size(c); - CHECK_EQ(SCMap::ClassID(s), c); - if (c != SCMap::kNumClasses - 1) - CHECK_EQ(SCMap::ClassID(s + 1), c + 1); - CHECK_EQ(SCMap::ClassID(s - 1), c); - if (c) - CHECK_GT(SCMap::Size(c), SCMap::Size(c-1)); - } - CHECK_EQ(SCMap::ClassID(SCMap::kMaxSize + 1), 0); - - for (uptr s = 1; s <= SCMap::kMaxSize; s++) { - uptr c = SCMap::ClassID(s); - CHECK_LT(c, SCMap::kNumClasses); - CHECK_GE(SCMap::Size(c), s); - if (c > 0) - CHECK_LT(SCMap::Size(c-1), s); - } -} - -TEST(SanitizerCommon, SizeClassAllocator64) { - Allocator a; - a.Init(); - - static const uptr sizes[] = {1, 16, 30, 40, 100, 1000, 10000, - 50000, 60000, 100000, 300000, 500000, 1000000, 2000000}; - - std::vector<void *> allocated; - - uptr last_total_allocated = 0; - for (int i = 0; i < 5; i++) { - // Allocate a bunch of chunks. - for (uptr s = 0; s < sizeof(sizes) /sizeof(sizes[0]); s++) { - uptr size = sizes[s]; - // printf("s = %ld\n", size); - uptr n_iter = std::max((uptr)2, 1000000 / size); - for (uptr i = 0; i < n_iter; i++) { - void *x = a.Allocate(size, 1); - allocated.push_back(x); - CHECK(a.PointerIsMine(x)); - CHECK_GE(a.GetActuallyAllocatedSize(x), size); - uptr class_id = a.GetSizeClass(x); - CHECK_EQ(class_id, SCMap::ClassID(size)); - uptr *metadata = reinterpret_cast<uptr*>(a.GetMetaData(x)); - metadata[0] = reinterpret_cast<uptr>(x) + 1; - metadata[1] = 0xABCD; - } - } - // Deallocate all. - for (uptr i = 0; i < allocated.size(); i++) { - void *x = allocated[i]; - uptr *metadata = reinterpret_cast<uptr*>(a.GetMetaData(x)); - CHECK_EQ(metadata[0], reinterpret_cast<uptr>(x) + 1); - CHECK_EQ(metadata[1], 0xABCD); - a.Deallocate(x); - } - allocated.clear(); - uptr total_allocated = a.TotalMemoryUsed(); - if (last_total_allocated == 0) - last_total_allocated = total_allocated; - CHECK_EQ(last_total_allocated, total_allocated); - } - - a.TestOnlyUnmap(); -} - - -TEST(SanitizerCommon, SizeClassAllocator64MetadataStress) { - Allocator a; - a.Init(); - static volatile void *sink; - - const uptr kNumAllocs = 10000; - void *allocated[kNumAllocs]; - for (uptr i = 0; i < kNumAllocs; i++) { - uptr size = (i % 4096) + 1; - void *x = a.Allocate(size, 1); - allocated[i] = x; - } - // Get Metadata kNumAllocs^2 times. - for (uptr i = 0; i < kNumAllocs * kNumAllocs; i++) { - sink = a.GetMetaData(allocated[i % kNumAllocs]); - } - for (uptr i = 0; i < kNumAllocs; i++) { - a.Deallocate(allocated[i]); - } - - a.TestOnlyUnmap(); - (void)sink; -} - -void FailInAssertionOnOOM() { - Allocator a; - a.Init(); - const uptr size = 1 << 20; - for (int i = 0; i < 1000000; i++) { - a.Allocate(size, 1); - } - - a.TestOnlyUnmap(); -} - -TEST(SanitizerCommon, SizeClassAllocator64Overflow) { - EXPECT_DEATH(FailInAssertionOnOOM(), - "allocated_user.*allocated_meta.*kRegionSize"); -} - -TEST(SanitizerCommon, LargeMmapAllocator) { - LargeMmapAllocator a; - a.Init(); - - static const int kNumAllocs = 100; - void *allocated[kNumAllocs]; - static const uptr size = 1000; - // Allocate some. - for (int i = 0; i < kNumAllocs; i++) { - allocated[i] = a.Allocate(size, 1); - } - // Deallocate all. - CHECK_GT(a.TotalMemoryUsed(), size * kNumAllocs); - for (int i = 0; i < kNumAllocs; i++) { - void *p = allocated[i]; - CHECK(a.PointerIsMine(p)); - a.Deallocate(p); - } - // Check that non left. - CHECK_EQ(a.TotalMemoryUsed(), 0); - - // Allocate some more, also add metadata. - for (int i = 0; i < kNumAllocs; i++) { - void *x = a.Allocate(size, 1); - CHECK_GE(a.GetActuallyAllocatedSize(x), size); - uptr *meta = reinterpret_cast<uptr*>(a.GetMetaData(x)); - *meta = i; - allocated[i] = x; - } - CHECK_GT(a.TotalMemoryUsed(), size * kNumAllocs); - // Deallocate all in reverse order. - for (int i = 0; i < kNumAllocs; i++) { - int idx = kNumAllocs - i - 1; - void *p = allocated[idx]; - uptr *meta = reinterpret_cast<uptr*>(a.GetMetaData(p)); - CHECK_EQ(*meta, idx); - CHECK(a.PointerIsMine(p)); - a.Deallocate(p); - } - CHECK_EQ(a.TotalMemoryUsed(), 0); -} - -TEST(SanitizerCommon, CombinedAllocator) { - typedef Allocator PrimaryAllocator; - typedef LargeMmapAllocator SecondaryAllocator; - typedef CombinedAllocator<PrimaryAllocator, AllocatorCache, - SecondaryAllocator> Allocator; - - AllocatorCache cache; - Allocator a; - a.Init(); - cache.Init(); - const uptr kNumAllocs = 100000; - const uptr kNumIter = 10; - for (uptr iter = 0; iter < kNumIter; iter++) { - std::vector<void*> allocated; - for (uptr i = 0; i < kNumAllocs; i++) { - uptr size = (i % (1 << 14)) + 1; - if ((i % 1024) == 0) - size = 1 << (10 + (i % 14)); - void *x = a.Allocate(&cache, size, 1); - uptr *meta = reinterpret_cast<uptr*>(a.GetMetaData(x)); - CHECK_EQ(*meta, 0); - *meta = size; - allocated.push_back(x); - } - - random_shuffle(allocated.begin(), allocated.end()); - - for (uptr i = 0; i < kNumAllocs; i++) { - void *x = allocated[i]; - uptr *meta = reinterpret_cast<uptr*>(a.GetMetaData(x)); - CHECK_NE(*meta, 0); - CHECK(a.PointerIsMine(x)); - *meta = 0; - a.Deallocate(&cache, x); - } - allocated.clear(); - a.SwallowCache(&cache); - } - a.TestOnlyUnmap(); -} - -static THREADLOCAL AllocatorCache static_allocator_cache; - -TEST(SanitizerCommon, SizeClassAllocatorLocalCache) { - static_allocator_cache.Init(); - - Allocator a; - AllocatorCache cache; - - a.Init(); - cache.Init(); - - const uptr kNumAllocs = 10000; - const int kNumIter = 100; - uptr saved_total = 0; - for (int i = 0; i < kNumIter; i++) { - void *allocated[kNumAllocs]; - for (uptr i = 0; i < kNumAllocs; i++) { - allocated[i] = cache.Allocate(&a, 0); - } - for (uptr i = 0; i < kNumAllocs; i++) { - cache.Deallocate(&a, 0, allocated[i]); - } - cache.Drain(&a); - uptr total_allocated = a.TotalMemoryUsed(); - if (saved_total) - CHECK_EQ(saved_total, total_allocated); - saved_total = total_allocated; - } - - a.TestOnlyUnmap(); -} diff --git a/lib/sanitizer_common/tests/sanitizer_allocator64_testlib.cc b/lib/sanitizer_common/tests/sanitizer_allocator64_testlib.cc deleted file mode 100644 index cff782342a6a..000000000000 --- a/lib/sanitizer_common/tests/sanitizer_allocator64_testlib.cc +++ /dev/null @@ -1,99 +0,0 @@ -//===-- sanitizer_allocator64_testlib.cc ----------------------------------===// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// -// Malloc replacement library based on CombinedAllocator. -// The primary purpose of this file is an end-to-end integration test -// for CombinedAllocator. -//===----------------------------------------------------------------------===// -#include "sanitizer_common/sanitizer_allocator64.h" -#include <stddef.h> -#include <stdio.h> -#include <unistd.h> -#include <assert.h> - -namespace { -static const uptr kAllocatorSpace = 0x600000000000ULL; -static const uptr kAllocatorSize = 0x10000000000; // 1T. - -typedef SizeClassAllocator64<kAllocatorSpace, kAllocatorSize, 16, - DefaultSizeClassMap> PrimaryAllocator; -typedef SizeClassAllocatorLocalCache<PrimaryAllocator::kNumClasses, - PrimaryAllocator> AllocatorCache; -typedef LargeMmapAllocator SecondaryAllocator; -typedef CombinedAllocator<PrimaryAllocator, AllocatorCache, - SecondaryAllocator> Allocator; - -static THREADLOCAL AllocatorCache cache; -static Allocator allocator; - -static int inited = 0; - -__attribute__((constructor)) -void Init() { - if (inited) return; - inited = true; // this must happen before any threads are created. - allocator.Init(); -} - -} // namespace - -namespace __sanitizer { -void NORETURN Die() { - _exit(77); -} -void NORETURN CheckFailed(const char *file, int line, const char *cond, - u64 v1, u64 v2) { - fprintf(stderr, "CheckFailed: %s:%d %s (%lld %lld)\n", - file, line, cond, v1, v2); - Die(); -} -} - -#if 1 -extern "C" { -void *malloc(size_t size) { - Init(); - assert(inited); - return allocator.Allocate(&cache, size, 8); -} - -void free(void *p) { - assert(inited); - allocator.Deallocate(&cache, p); -} - -void *calloc(size_t nmemb, size_t size) { - assert(inited); - return allocator.Allocate(&cache, nmemb * size, 8, /*cleared=*/true); -} - -void *realloc(void *p, size_t new_size) { - assert(inited); - return allocator.Reallocate(&cache, p, new_size, 8); -} - -void *memalign() { assert(0); } - -int posix_memalign(void **memptr, size_t alignment, size_t size) { - *memptr = allocator.Allocate(&cache, size, alignment); - CHECK_EQ(((uptr)*memptr & (alignment - 1)), 0); - return 0; -} - -void *valloc(size_t size) { - assert(inited); - return allocator.Allocate(&cache, size, kPageSize); -} - -void *pvalloc(size_t size) { - assert(inited); - if (size == 0) size = kPageSize; - return allocator.Allocate(&cache, size, kPageSize); -} -} -#endif diff --git a/lib/sanitizer_common/tests/sanitizer_allocator_test.cc b/lib/sanitizer_common/tests/sanitizer_allocator_test.cc index d6c7f56dc143..d67f4636ef4f 100644 --- a/lib/sanitizer_common/tests/sanitizer_allocator_test.cc +++ b/lib/sanitizer_common/tests/sanitizer_allocator_test.cc @@ -8,13 +8,465 @@ //===----------------------------------------------------------------------===// // // This file is a part of ThreadSanitizer/AddressSanitizer runtime. +// Tests for sanitizer_allocator.h. // //===----------------------------------------------------------------------===// +#include "sanitizer_common/sanitizer_allocator.h" #include "sanitizer_common/sanitizer_common.h" + +#include "sanitizer_test_utils.h" + #include "gtest/gtest.h" + #include <stdlib.h> +#include <pthread.h> +#include <algorithm> +#include <vector> + +// Too slow for debug build +#if TSAN_DEBUG == 0 + +#if SANITIZER_WORDSIZE == 64 +static const uptr kAllocatorSpace = 0x700000000000ULL; +static const uptr kAllocatorSize = 0x010000000000ULL; // 1T. +static const u64 kAddressSpaceSize = 1ULL << 47; + +typedef SizeClassAllocator64< + kAllocatorSpace, kAllocatorSize, 16, DefaultSizeClassMap> Allocator64; + +typedef SizeClassAllocator64< + kAllocatorSpace, kAllocatorSize, 16, CompactSizeClassMap> Allocator64Compact; +#else +static const u64 kAddressSpaceSize = 1ULL << 32; +#endif + +typedef SizeClassAllocator32< + 0, kAddressSpaceSize, 16, CompactSizeClassMap> Allocator32Compact; + +template <class SizeClassMap> +void TestSizeClassMap() { + typedef SizeClassMap SCMap; + // SCMap::Print(); + SCMap::Validate(); +} + +TEST(SanitizerCommon, DefaultSizeClassMap) { + TestSizeClassMap<DefaultSizeClassMap>(); +} + +TEST(SanitizerCommon, CompactSizeClassMap) { + TestSizeClassMap<CompactSizeClassMap>(); +} -namespace __sanitizer { +template <class Allocator> +void TestSizeClassAllocator() { + Allocator *a = new Allocator; + a->Init(); + SizeClassAllocatorLocalCache<Allocator> cache; + cache.Init(); + + static const uptr sizes[] = {1, 16, 30, 40, 100, 1000, 10000, + 50000, 60000, 100000, 120000, 300000, 500000, 1000000, 2000000}; + + std::vector<void *> allocated; + + uptr last_total_allocated = 0; + for (int i = 0; i < 3; i++) { + // Allocate a bunch of chunks. + for (uptr s = 0; s < ARRAY_SIZE(sizes); s++) { + uptr size = sizes[s]; + if (!a->CanAllocate(size, 1)) continue; + // printf("s = %ld\n", size); + uptr n_iter = std::max((uptr)6, 10000000 / size); + // fprintf(stderr, "size: %ld iter: %ld\n", size, n_iter); + for (uptr i = 0; i < n_iter; i++) { + uptr class_id0 = Allocator::SizeClassMapT::ClassID(size); + char *x = (char*)cache.Allocate(a, class_id0); + x[0] = 0; + x[size - 1] = 0; + x[size / 2] = 0; + allocated.push_back(x); + CHECK_EQ(x, a->GetBlockBegin(x)); + CHECK_EQ(x, a->GetBlockBegin(x + size - 1)); + CHECK(a->PointerIsMine(x)); + CHECK(a->PointerIsMine(x + size - 1)); + CHECK(a->PointerIsMine(x + size / 2)); + CHECK_GE(a->GetActuallyAllocatedSize(x), size); + uptr class_id = a->GetSizeClass(x); + CHECK_EQ(class_id, Allocator::SizeClassMapT::ClassID(size)); + uptr *metadata = reinterpret_cast<uptr*>(a->GetMetaData(x)); + metadata[0] = reinterpret_cast<uptr>(x) + 1; + metadata[1] = 0xABCD; + } + } + // Deallocate all. + for (uptr i = 0; i < allocated.size(); i++) { + void *x = allocated[i]; + uptr *metadata = reinterpret_cast<uptr*>(a->GetMetaData(x)); + CHECK_EQ(metadata[0], reinterpret_cast<uptr>(x) + 1); + CHECK_EQ(metadata[1], 0xABCD); + cache.Deallocate(a, a->GetSizeClass(x), x); + } + allocated.clear(); + uptr total_allocated = a->TotalMemoryUsed(); + if (last_total_allocated == 0) + last_total_allocated = total_allocated; + CHECK_EQ(last_total_allocated, total_allocated); + } + + a->TestOnlyUnmap(); + delete a; +} + +#if SANITIZER_WORDSIZE == 64 +TEST(SanitizerCommon, SizeClassAllocator64) { + TestSizeClassAllocator<Allocator64>(); +} + +TEST(SanitizerCommon, SizeClassAllocator64Compact) { + TestSizeClassAllocator<Allocator64Compact>(); +} +#endif + +TEST(SanitizerCommon, SizeClassAllocator32Compact) { + TestSizeClassAllocator<Allocator32Compact>(); +} + +template <class Allocator> +void SizeClassAllocatorMetadataStress() { + Allocator *a = new Allocator; + a->Init(); + SizeClassAllocatorLocalCache<Allocator> cache; + cache.Init(); + static volatile void *sink; + + const uptr kNumAllocs = 10000; + void *allocated[kNumAllocs]; + for (uptr i = 0; i < kNumAllocs; i++) { + void *x = cache.Allocate(a, 1 + i % 50); + allocated[i] = x; + } + // Get Metadata kNumAllocs^2 times. + for (uptr i = 0; i < kNumAllocs * kNumAllocs; i++) { + sink = a->GetMetaData(allocated[i % kNumAllocs]); + } + for (uptr i = 0; i < kNumAllocs; i++) { + cache.Deallocate(a, 1 + i % 50, allocated[i]); + } + + a->TestOnlyUnmap(); + (void)sink; + delete a; +} + +#if SANITIZER_WORDSIZE == 64 +TEST(SanitizerCommon, SizeClassAllocator64MetadataStress) { + SizeClassAllocatorMetadataStress<Allocator64>(); +} + +TEST(SanitizerCommon, SizeClassAllocator64CompactMetadataStress) { + SizeClassAllocatorMetadataStress<Allocator64Compact>(); +} +#endif +TEST(SanitizerCommon, SizeClassAllocator32CompactMetadataStress) { + SizeClassAllocatorMetadataStress<Allocator32Compact>(); +} + +struct TestMapUnmapCallback { + static int map_count, unmap_count; + void OnMap(uptr p, uptr size) const { map_count++; } + void OnUnmap(uptr p, uptr size) const { unmap_count++; } +}; +int TestMapUnmapCallback::map_count; +int TestMapUnmapCallback::unmap_count; + +#if SANITIZER_WORDSIZE == 64 +TEST(SanitizerCommon, SizeClassAllocator64MapUnmapCallback) { + TestMapUnmapCallback::map_count = 0; + TestMapUnmapCallback::unmap_count = 0; + typedef SizeClassAllocator64< + kAllocatorSpace, kAllocatorSize, 16, DefaultSizeClassMap, + TestMapUnmapCallback> Allocator64WithCallBack; + Allocator64WithCallBack *a = new Allocator64WithCallBack; + a->Init(); + EXPECT_EQ(TestMapUnmapCallback::map_count, 1); // Allocator state. + SizeClassAllocatorLocalCache<Allocator64WithCallBack> cache; + cache.Init(); + a->AllocateBatch(&cache, 64); + EXPECT_EQ(TestMapUnmapCallback::map_count, 3); // State + alloc + metadata. + a->TestOnlyUnmap(); + EXPECT_EQ(TestMapUnmapCallback::unmap_count, 1); // The whole thing. + delete a; +} +#endif + +TEST(SanitizerCommon, SizeClassAllocator32MapUnmapCallback) { + TestMapUnmapCallback::map_count = 0; + TestMapUnmapCallback::unmap_count = 0; + typedef SizeClassAllocator32< + 0, kAddressSpaceSize, 16, CompactSizeClassMap, + TestMapUnmapCallback> Allocator32WithCallBack; + Allocator32WithCallBack *a = new Allocator32WithCallBack; + a->Init(); + EXPECT_EQ(TestMapUnmapCallback::map_count, 1); // Allocator state. + SizeClassAllocatorLocalCache<Allocator32WithCallBack> cache; + cache.Init(); + a->AllocateBatch(&cache, 64); + EXPECT_EQ(TestMapUnmapCallback::map_count, 2); // alloc. + a->TestOnlyUnmap(); + EXPECT_EQ(TestMapUnmapCallback::unmap_count, 2); // The whole thing + alloc. + delete a; + // fprintf(stderr, "Map: %d Unmap: %d\n", + // TestMapUnmapCallback::map_count, + // TestMapUnmapCallback::unmap_count); +} + +TEST(SanitizerCommon, LargeMmapAllocatorMapUnmapCallback) { + TestMapUnmapCallback::map_count = 0; + TestMapUnmapCallback::unmap_count = 0; + LargeMmapAllocator<TestMapUnmapCallback> a; + a.Init(); + void *x = a.Allocate(1 << 20, 1); + EXPECT_EQ(TestMapUnmapCallback::map_count, 1); + a.Deallocate(x); + EXPECT_EQ(TestMapUnmapCallback::unmap_count, 1); +} + +template<class Allocator> +void FailInAssertionOnOOM() { + Allocator a; + a.Init(); + SizeClassAllocatorLocalCache<Allocator> cache; + cache.Init(); + for (int i = 0; i < 1000000; i++) { + a.AllocateBatch(&cache, 64); + } + + a.TestOnlyUnmap(); +} + +#if SANITIZER_WORDSIZE == 64 +TEST(SanitizerCommon, SizeClassAllocator64Overflow) { + EXPECT_DEATH(FailInAssertionOnOOM<Allocator64>(), "Out of memory"); +} +#endif + +TEST(SanitizerCommon, LargeMmapAllocator) { + LargeMmapAllocator<> a; + a.Init(); + + static const int kNumAllocs = 1000; + char *allocated[kNumAllocs]; + static const uptr size = 4000; + // Allocate some. + for (int i = 0; i < kNumAllocs; i++) { + allocated[i] = (char *)a.Allocate(size, 1); + CHECK(a.PointerIsMine(allocated[i])); + } + // Deallocate all. + CHECK_GT(a.TotalMemoryUsed(), size * kNumAllocs); + for (int i = 0; i < kNumAllocs; i++) { + char *p = allocated[i]; + CHECK(a.PointerIsMine(p)); + a.Deallocate(p); + } + // Check that non left. + CHECK_EQ(a.TotalMemoryUsed(), 0); + + // Allocate some more, also add metadata. + for (int i = 0; i < kNumAllocs; i++) { + char *x = (char *)a.Allocate(size, 1); + CHECK_GE(a.GetActuallyAllocatedSize(x), size); + uptr *meta = reinterpret_cast<uptr*>(a.GetMetaData(x)); + *meta = i; + allocated[i] = x; + } + for (int i = 0; i < kNumAllocs * kNumAllocs; i++) { + char *p = allocated[i % kNumAllocs]; + CHECK(a.PointerIsMine(p)); + CHECK(a.PointerIsMine(p + 2000)); + } + CHECK_GT(a.TotalMemoryUsed(), size * kNumAllocs); + // Deallocate all in reverse order. + for (int i = 0; i < kNumAllocs; i++) { + int idx = kNumAllocs - i - 1; + char *p = allocated[idx]; + uptr *meta = reinterpret_cast<uptr*>(a.GetMetaData(p)); + CHECK_EQ(*meta, idx); + CHECK(a.PointerIsMine(p)); + a.Deallocate(p); + } + CHECK_EQ(a.TotalMemoryUsed(), 0); + + // Test alignments. + uptr max_alignment = SANITIZER_WORDSIZE == 64 ? (1 << 28) : (1 << 24); + for (uptr alignment = 8; alignment <= max_alignment; alignment *= 2) { + const uptr kNumAlignedAllocs = 100; + for (uptr i = 0; i < kNumAlignedAllocs; i++) { + uptr size = ((i % 10) + 1) * 4096; + char *p = allocated[i] = (char *)a.Allocate(size, alignment); + CHECK_EQ(p, a.GetBlockBegin(p)); + CHECK_EQ(p, a.GetBlockBegin(p + size - 1)); + CHECK_EQ(p, a.GetBlockBegin(p + size / 2)); + CHECK_EQ(0, (uptr)allocated[i] % alignment); + p[0] = p[size - 1] = 0; + } + for (uptr i = 0; i < kNumAlignedAllocs; i++) { + a.Deallocate(allocated[i]); + } + } +} + +template +<class PrimaryAllocator, class SecondaryAllocator, class AllocatorCache> +void TestCombinedAllocator() { + typedef + CombinedAllocator<PrimaryAllocator, AllocatorCache, SecondaryAllocator> + Allocator; + Allocator *a = new Allocator; + a->Init(); + + AllocatorCache cache; + cache.Init(); + + EXPECT_EQ(a->Allocate(&cache, -1, 1), (void*)0); + EXPECT_EQ(a->Allocate(&cache, -1, 1024), (void*)0); + EXPECT_EQ(a->Allocate(&cache, (uptr)-1 - 1024, 1), (void*)0); + EXPECT_EQ(a->Allocate(&cache, (uptr)-1 - 1024, 1024), (void*)0); + EXPECT_EQ(a->Allocate(&cache, (uptr)-1 - 1023, 1024), (void*)0); + + const uptr kNumAllocs = 100000; + const uptr kNumIter = 10; + for (uptr iter = 0; iter < kNumIter; iter++) { + std::vector<void*> allocated; + for (uptr i = 0; i < kNumAllocs; i++) { + uptr size = (i % (1 << 14)) + 1; + if ((i % 1024) == 0) + size = 1 << (10 + (i % 14)); + void *x = a->Allocate(&cache, size, 1); + uptr *meta = reinterpret_cast<uptr*>(a->GetMetaData(x)); + CHECK_EQ(*meta, 0); + *meta = size; + allocated.push_back(x); + } + + random_shuffle(allocated.begin(), allocated.end()); + + for (uptr i = 0; i < kNumAllocs; i++) { + void *x = allocated[i]; + uptr *meta = reinterpret_cast<uptr*>(a->GetMetaData(x)); + CHECK_NE(*meta, 0); + CHECK(a->PointerIsMine(x)); + *meta = 0; + a->Deallocate(&cache, x); + } + allocated.clear(); + a->SwallowCache(&cache); + } + a->TestOnlyUnmap(); +} + +#if SANITIZER_WORDSIZE == 64 +TEST(SanitizerCommon, CombinedAllocator64) { + TestCombinedAllocator<Allocator64, + LargeMmapAllocator<>, + SizeClassAllocatorLocalCache<Allocator64> > (); +} + +TEST(SanitizerCommon, CombinedAllocator64Compact) { + TestCombinedAllocator<Allocator64Compact, + LargeMmapAllocator<>, + SizeClassAllocatorLocalCache<Allocator64Compact> > (); +} +#endif + +TEST(SanitizerCommon, CombinedAllocator32Compact) { + TestCombinedAllocator<Allocator32Compact, + LargeMmapAllocator<>, + SizeClassAllocatorLocalCache<Allocator32Compact> > (); +} + +template <class AllocatorCache> +void TestSizeClassAllocatorLocalCache() { + static AllocatorCache static_allocator_cache; + static_allocator_cache.Init(); + AllocatorCache cache; + typedef typename AllocatorCache::Allocator Allocator; + Allocator *a = new Allocator(); + + a->Init(); + cache.Init(); + + const uptr kNumAllocs = 10000; + const int kNumIter = 100; + uptr saved_total = 0; + for (int class_id = 1; class_id <= 5; class_id++) { + for (int it = 0; it < kNumIter; it++) { + void *allocated[kNumAllocs]; + for (uptr i = 0; i < kNumAllocs; i++) { + allocated[i] = cache.Allocate(a, class_id); + } + for (uptr i = 0; i < kNumAllocs; i++) { + cache.Deallocate(a, class_id, allocated[i]); + } + cache.Drain(a); + uptr total_allocated = a->TotalMemoryUsed(); + if (it) + CHECK_EQ(saved_total, total_allocated); + saved_total = total_allocated; + } + } + + a->TestOnlyUnmap(); + delete a; +} + +#if SANITIZER_WORDSIZE == 64 +TEST(SanitizerCommon, SizeClassAllocator64LocalCache) { + TestSizeClassAllocatorLocalCache< + SizeClassAllocatorLocalCache<Allocator64> >(); +} + +TEST(SanitizerCommon, SizeClassAllocator64CompactLocalCache) { + TestSizeClassAllocatorLocalCache< + SizeClassAllocatorLocalCache<Allocator64Compact> >(); +} +#endif + +TEST(SanitizerCommon, SizeClassAllocator32CompactLocalCache) { + TestSizeClassAllocatorLocalCache< + SizeClassAllocatorLocalCache<Allocator32Compact> >(); +} + +#if SANITIZER_WORDSIZE == 64 +typedef SizeClassAllocatorLocalCache<Allocator64> AllocatorCache; +static AllocatorCache static_allocator_cache; + +void *AllocatorLeakTestWorker(void *arg) { + typedef AllocatorCache::Allocator Allocator; + Allocator *a = (Allocator*)(arg); + static_allocator_cache.Allocate(a, 10); + static_allocator_cache.Drain(a); + return 0; +} + +TEST(SanitizerCommon, AllocatorLeakTest) { + typedef AllocatorCache::Allocator Allocator; + Allocator a; + a.Init(); + uptr total_used_memory = 0; + for (int i = 0; i < 100; i++) { + pthread_t t; + EXPECT_EQ(0, pthread_create(&t, 0, AllocatorLeakTestWorker, &a)); + EXPECT_EQ(0, pthread_join(t, 0)); + if (i == 0) + total_used_memory = a.TotalMemoryUsed(); + EXPECT_EQ(a.TotalMemoryUsed(), total_used_memory); + } + + a.TestOnlyUnmap(); +} +#endif TEST(Allocator, Basic) { char *p = (char*)InternalAlloc(10); @@ -22,14 +474,6 @@ TEST(Allocator, Basic) { char *p2 = (char*)InternalAlloc(20); EXPECT_NE(p2, (char*)0); EXPECT_NE(p2, p); - for (int i = 0; i < 10; i++) { - p[i] = 42; - EXPECT_EQ(p, InternalAllocBlock(p + i)); - } - for (int i = 0; i < 20; i++) { - ((char*)p2)[i] = 42; - EXPECT_EQ(p2, InternalAllocBlock(p2 + i)); - } InternalFree(p); InternalFree(p2); } @@ -39,13 +483,9 @@ TEST(Allocator, Stress) { char *ptrs[kCount]; unsigned rnd = 42; for (int i = 0; i < kCount; i++) { - uptr sz = rand_r(&rnd) % 1000; + uptr sz = my_rand_r(&rnd) % 1000; char *p = (char*)InternalAlloc(sz); EXPECT_NE(p, (char*)0); - for (uptr j = 0; j < sz; j++) { - p[j] = 42; - EXPECT_EQ(p, InternalAllocBlock(p + j)); - } ptrs[i] = p; } for (int i = 0; i < kCount; i++) { @@ -53,4 +493,18 @@ TEST(Allocator, Stress) { } } -} // namespace __sanitizer +TEST(Allocator, ScopedBuffer) { + const int kSize = 512; + { + InternalScopedBuffer<int> int_buf(kSize); + EXPECT_EQ(sizeof(int) * kSize, int_buf.size()); // NOLINT + } + InternalScopedBuffer<char> char_buf(kSize); + EXPECT_EQ(sizeof(char) * kSize, char_buf.size()); // NOLINT + internal_memset(char_buf.data(), 'c', kSize); + for (int i = 0; i < kSize; i++) { + EXPECT_EQ('c', char_buf[i]); + } +} + +#endif // #if TSAN_DEBUG==0 diff --git a/lib/sanitizer_common/tests/sanitizer_allocator_testlib.cc b/lib/sanitizer_common/tests/sanitizer_allocator_testlib.cc new file mode 100644 index 000000000000..f6a944f68f5e --- /dev/null +++ b/lib/sanitizer_common/tests/sanitizer_allocator_testlib.cc @@ -0,0 +1,162 @@ +//===-- sanitizer_allocator_testlib.cc ------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// Malloc replacement library based on CombinedAllocator. +// The primary purpose of this file is an end-to-end integration test +// for CombinedAllocator. +//===----------------------------------------------------------------------===// +/* Usage: +clang++ -fno-exceptions -g -fPIC -I. -I../include -Isanitizer \ + sanitizer_common/tests/sanitizer_allocator_testlib.cc \ + sanitizer_common/sanitizer_*.cc -shared -lpthread -o testmalloc.so +LD_PRELOAD=`pwd`/testmalloc.so /your/app +*/ +#include "sanitizer_common/sanitizer_allocator.h" +#include "sanitizer_common/sanitizer_common.h" +#include <stddef.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <pthread.h> + +#ifndef SANITIZER_MALLOC_HOOK +# define SANITIZER_MALLOC_HOOK(p, s) +#endif + +#ifndef SANITIZER_FREE_HOOK +# define SANITIZER_FREE_HOOK(p) +#endif + +namespace { +static const uptr kAllocatorSpace = 0x600000000000ULL; +static const uptr kAllocatorSize = 0x10000000000ULL; // 1T. + +typedef SizeClassAllocator64<kAllocatorSpace, kAllocatorSize, 0, + CompactSizeClassMap> PrimaryAllocator; +typedef SizeClassAllocatorLocalCache<PrimaryAllocator> AllocatorCache; +typedef LargeMmapAllocator<> SecondaryAllocator; +typedef CombinedAllocator<PrimaryAllocator, AllocatorCache, + SecondaryAllocator> Allocator; + +static Allocator allocator; +static bool global_inited; +static THREADLOCAL AllocatorCache cache; +static THREADLOCAL bool thread_inited; +static pthread_key_t pkey; + +static void thread_dtor(void *v) { + if ((uptr)v != 3) { + pthread_setspecific(pkey, (void*)((uptr)v + 1)); + return; + } + allocator.SwallowCache(&cache); +} + +static void NOINLINE thread_init() { + if (!global_inited) { + global_inited = true; + allocator.Init(); + pthread_key_create(&pkey, thread_dtor); + } + thread_inited = true; + pthread_setspecific(pkey, (void*)1); + cache.Init(); +} +} // namespace + +extern "C" { +void *malloc(size_t size) { + if (UNLIKELY(!thread_inited)) + thread_init(); + void *p = allocator.Allocate(&cache, size, 8); + SANITIZER_MALLOC_HOOK(p, size); + return p; +} + +void free(void *p) { + if (UNLIKELY(!thread_inited)) + thread_init(); + SANITIZER_FREE_HOOK(p); + allocator.Deallocate(&cache, p); +} + +void *calloc(size_t nmemb, size_t size) { + if (UNLIKELY(!thread_inited)) + thread_init(); + size *= nmemb; + void *p = allocator.Allocate(&cache, size, 8, false); + memset(p, 0, size); + SANITIZER_MALLOC_HOOK(p, size); + return p; +} + +void *realloc(void *p, size_t size) { + if (UNLIKELY(!thread_inited)) + thread_init(); + if (p) { + SANITIZER_FREE_HOOK(p); + } + p = allocator.Reallocate(&cache, p, size, 8); + if (p) { + SANITIZER_MALLOC_HOOK(p, size); + } + return p; +} + +void *memalign(size_t alignment, size_t size) { + if (UNLIKELY(!thread_inited)) + thread_init(); + void *p = allocator.Allocate(&cache, size, alignment); + SANITIZER_MALLOC_HOOK(p, size); + return p; +} + +int posix_memalign(void **memptr, size_t alignment, size_t size) { + if (UNLIKELY(!thread_inited)) + thread_init(); + *memptr = allocator.Allocate(&cache, size, alignment); + SANITIZER_MALLOC_HOOK(*memptr, size); + return 0; +} + +void *valloc(size_t size) { + if (UNLIKELY(!thread_inited)) + thread_init(); + if (size == 0) + size = GetPageSizeCached(); + void *p = allocator.Allocate(&cache, size, GetPageSizeCached()); + SANITIZER_MALLOC_HOOK(p, size); + return p; +} + +void cfree(void *p) ALIAS("free"); +void *pvalloc(size_t size) ALIAS("valloc"); +void *__libc_memalign(size_t alignment, size_t size) ALIAS("memalign"); + +void malloc_usable_size() { +} + +void mallinfo() { +} + +void mallopt() { +} +} // extern "C" + +namespace std { + struct nothrow_t; +} + +void *operator new(size_t size) ALIAS("malloc"); +void *operator new[](size_t size) ALIAS("malloc"); +void *operator new(size_t size, std::nothrow_t const&) ALIAS("malloc"); +void *operator new[](size_t size, std::nothrow_t const&) ALIAS("malloc"); +void operator delete(void *ptr) ALIAS("free"); +void operator delete[](void *ptr) ALIAS("free"); +void operator delete(void *ptr, std::nothrow_t const&) ALIAS("free"); +void operator delete[](void *ptr, std::nothrow_t const&) ALIAS("free"); diff --git a/lib/sanitizer_common/tests/sanitizer_common_test.cc b/lib/sanitizer_common/tests/sanitizer_common_test.cc index 91570dcc99e0..01d8b5a87c01 100644 --- a/lib/sanitizer_common/tests/sanitizer_common_test.cc +++ b/lib/sanitizer_common/tests/sanitizer_common_test.cc @@ -11,6 +11,7 @@ // //===----------------------------------------------------------------------===// #include "sanitizer_common/sanitizer_common.h" +#include "sanitizer_common/sanitizer_libc.h" #include "gtest/gtest.h" namespace __sanitizer { @@ -63,4 +64,36 @@ TEST(SanitizerCommon, SortTest) { EXPECT_TRUE(IsSorted(array, 2)); } +TEST(SanitizerCommon, MmapAlignedOrDie) { + uptr PageSize = GetPageSizeCached(); + for (uptr size = 1; size <= 32; size *= 2) { + for (uptr alignment = 1; alignment <= 32; alignment *= 2) { + for (int iter = 0; iter < 100; iter++) { + uptr res = (uptr)MmapAlignedOrDie( + size * PageSize, alignment * PageSize, "MmapAlignedOrDieTest"); + EXPECT_EQ(0U, res % (alignment * PageSize)); + internal_memset((void*)res, 1, size * PageSize); + UnmapOrDie((void*)res, size * PageSize); + } + } + } +} + +#ifdef __linux__ +TEST(SanitizerCommon, SanitizerSetThreadName) { + const char *names[] = { + "0123456789012", + "01234567890123", + "012345678901234", // Larger names will be truncated on linux. + }; + + for (size_t i = 0; i < ARRAY_SIZE(names); i++) { + EXPECT_TRUE(SanitizerSetThreadName(names[i])); + char buff[100]; + EXPECT_TRUE(SanitizerGetThreadName(buff, sizeof(buff) - 1)); + EXPECT_EQ(0, internal_strcmp(buff, names[i])); + } +} +#endif + } // namespace sanitizer diff --git a/lib/sanitizer_common/tests/sanitizer_flags_test.cc b/lib/sanitizer_common/tests/sanitizer_flags_test.cc index 4b273e5b9cef..c0589f4d2e90 100644 --- a/lib/sanitizer_common/tests/sanitizer_flags_test.cc +++ b/lib/sanitizer_common/tests/sanitizer_flags_test.cc @@ -12,11 +12,9 @@ //===----------------------------------------------------------------------===// #include "sanitizer_common/sanitizer_common.h" #include "sanitizer_common/sanitizer_flags.h" +#include "sanitizer_common/sanitizer_libc.h" #include "gtest/gtest.h" -#include "tsan_rtl.h" // FIXME: break dependency from TSan runtime. -using __tsan::ScopedInRtl; - #include <string.h> namespace __sanitizer { @@ -34,11 +32,10 @@ static void TestStrFlag(const char *start_value, const char *env, const char *final_value) { const char *flag = start_value; ParseFlag(env, &flag, kFlagName); - EXPECT_STREQ(final_value, flag); + EXPECT_EQ(internal_strcmp(final_value, flag), 0); } TEST(SanitizerCommon, BooleanFlags) { - ScopedInRtl in_rtl; TestFlag(true, "--flag_name", true); TestFlag(false, "flag_name", false); TestFlag(false, "--flag_name=1", true); @@ -51,7 +48,6 @@ TEST(SanitizerCommon, BooleanFlags) { } TEST(SanitizerCommon, IntFlags) { - ScopedInRtl in_rtl; TestFlag(-11, 0, -11); TestFlag(-11, "flag_name", 0); TestFlag(-11, "--flag_name=", 0); @@ -60,12 +56,12 @@ TEST(SanitizerCommon, IntFlags) { } TEST(SanitizerCommon, StrFlags) { - ScopedInRtl in_rtl; TestStrFlag("zzz", 0, "zzz"); TestStrFlag("zzz", "flag_name", ""); TestStrFlag("zzz", "--flag_name=", ""); TestStrFlag("", "--flag_name=abc", "abc"); TestStrFlag("", "--flag_name='abc zxc'", "abc zxc"); + TestStrFlag("", "--flag_name='abc zxcc'", "abc zxcc"); TestStrFlag("", "--flag_name=\"abc qwe\" asd", "abc qwe"); } diff --git a/lib/sanitizer_common/tests/sanitizer_libc_test.cc b/lib/sanitizer_common/tests/sanitizer_libc_test.cc new file mode 100644 index 000000000000..b9d8414e0cbf --- /dev/null +++ b/lib/sanitizer_common/tests/sanitizer_libc_test.cc @@ -0,0 +1,42 @@ +//===-- sanitizer_libc_test.cc --------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// Tests for sanitizer_libc.h. +//===----------------------------------------------------------------------===// + +#include "sanitizer_common/sanitizer_libc.h" +#include "gtest/gtest.h" + +// A regression test for internal_memmove() implementation. +TEST(SanitizerCommon, InternalMemmoveRegression) { + char src[] = "Hello World"; + char *dest = src + 6; + __sanitizer::internal_memmove(dest, src, 5); + EXPECT_EQ(dest[0], src[0]); + EXPECT_EQ(dest[4], src[4]); +} + +TEST(SanitizerCommon, mem_is_zero) { + size_t size = 128; + char *x = new char[size]; + memset(x, 0, size); + for (size_t pos = 0; pos < size; pos++) { + x[pos] = 1; + for (size_t beg = 0; beg < size; beg++) { + for (size_t end = beg; end < size; end++) { + // fprintf(stderr, "pos %zd beg %zd end %zd \n", pos, beg, end); + if (beg <= pos && pos < end) + EXPECT_FALSE(__sanitizer::mem_is_zero(x + beg, end - beg)); + else + EXPECT_TRUE(__sanitizer::mem_is_zero(x + beg, end - beg)); + } + } + x[pos] = 0; + } + delete [] x; +} diff --git a/lib/sanitizer_common/tests/sanitizer_list_test.cc b/lib/sanitizer_common/tests/sanitizer_list_test.cc index d328fbfdf92c..fbe53c0375c0 100644 --- a/lib/sanitizer_common/tests/sanitizer_list_test.cc +++ b/lib/sanitizer_common/tests/sanitizer_list_test.cc @@ -21,8 +21,7 @@ struct ListItem { typedef IntrusiveList<ListItem> List; -// Check that IntrusiveList can be made thread-local. -static THREADLOCAL List static_list; +static List static_list; static void SetList(List *l, ListItem *x = 0, ListItem *y = 0, ListItem *z = 0) { @@ -154,4 +153,21 @@ TEST(SanitizerCommon, IntrusiveList) { CHECK(l2.empty()); } +TEST(SanitizerCommon, IntrusiveListAppendEmpty) { + ListItem i; + List l; + l.clear(); + l.push_back(&i); + List l2; + l2.clear(); + l.append_back(&l2); + CHECK_EQ(l.back(), &i); + CHECK_EQ(l.front(), &i); + CHECK_EQ(l.size(), 1); + l.append_front(&l2); + CHECK_EQ(l.back(), &i); + CHECK_EQ(l.front(), &i); + CHECK_EQ(l.size(), 1); +} + } // namespace __sanitizer diff --git a/lib/sanitizer_common/tests/sanitizer_mutex_test.cc b/lib/sanitizer_common/tests/sanitizer_mutex_test.cc new file mode 100644 index 000000000000..6bb2ae29a188 --- /dev/null +++ b/lib/sanitizer_common/tests/sanitizer_mutex_test.cc @@ -0,0 +1,128 @@ +//===-- sanitizer_mutex_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. +// +//===----------------------------------------------------------------------===// +#include "sanitizer_common/sanitizer_mutex.h" +#include "sanitizer_common/sanitizer_common.h" +#include "gtest/gtest.h" + +#include <string.h> + +namespace __sanitizer { + +template<typename MutexType> +class TestData { + public: + explicit TestData(MutexType *mtx) + : mtx_(mtx) { + for (int i = 0; i < kSize; i++) + data_[i] = 0; + } + + void Write() { + Lock l(mtx_); + T v0 = data_[0]; + for (int i = 0; i < kSize; i++) { + CHECK_EQ(data_[i], v0); + data_[i]++; + } + } + + void TryWrite() { + if (!mtx_->TryLock()) + return; + T v0 = data_[0]; + for (int i = 0; i < kSize; i++) { + CHECK_EQ(data_[i], v0); + data_[i]++; + } + mtx_->Unlock(); + } + + void Backoff() { + volatile T data[kSize] = {}; + for (int i = 0; i < kSize; i++) { + data[i]++; + CHECK_EQ(data[i], 1); + } + } + + private: + typedef GenericScopedLock<MutexType> Lock; + static const int kSize = 64; + typedef u64 T; + MutexType *mtx_; + char pad_[kCacheLineSize]; + T data_[kSize]; +}; + +const int kThreads = 8; +const int kWriteRate = 1024; +#if SANITIZER_DEBUG +const int kIters = 16*1024; +#else +const int kIters = 64*1024; +#endif + +template<typename MutexType> +static void *lock_thread(void *param) { + TestData<MutexType> *data = (TestData<MutexType>*)param; + for (int i = 0; i < kIters; i++) { + data->Write(); + data->Backoff(); + } + return 0; +} + +template<typename MutexType> +static void *try_thread(void *param) { + TestData<MutexType> *data = (TestData<MutexType>*)param; + for (int i = 0; i < kIters; i++) { + data->TryWrite(); + data->Backoff(); + } + return 0; +} + +TEST(SanitizerCommon, SpinMutex) { + SpinMutex mtx; + mtx.Init(); + TestData<SpinMutex> data(&mtx); + pthread_t threads[kThreads]; + for (int i = 0; i < kThreads; i++) + pthread_create(&threads[i], 0, lock_thread<SpinMutex>, &data); + for (int i = 0; i < kThreads; i++) + pthread_join(threads[i], 0); +} + +TEST(SanitizerCommon, SpinMutexTry) { + SpinMutex mtx; + mtx.Init(); + TestData<SpinMutex> data(&mtx); + pthread_t threads[kThreads]; + for (int i = 0; i < kThreads; i++) + pthread_create(&threads[i], 0, try_thread<SpinMutex>, &data); + for (int i = 0; i < kThreads; i++) + pthread_join(threads[i], 0); +} + +TEST(SanitizerCommon, BlockingMutex) { + u64 mtxmem[1024] = {}; + BlockingMutex *mtx = new(mtxmem) BlockingMutex(LINKER_INITIALIZED); + TestData<BlockingMutex> data(mtx); + pthread_t threads[kThreads]; + for (int i = 0; i < kThreads; i++) + pthread_create(&threads[i], 0, lock_thread<BlockingMutex>, &data); + for (int i = 0; i < kThreads; i++) + pthread_join(threads[i], 0); +} + +} // namespace __sanitizer diff --git a/lib/tsan/unit_tests/tsan_printf_test.cc b/lib/sanitizer_common/tests/sanitizer_printf_test.cc index 0dfd1d2dfe42..b1889cd8794e 100644 --- a/lib/tsan/unit_tests/tsan_printf_test.cc +++ b/lib/sanitizer_common/tests/sanitizer_printf_test.cc @@ -1,4 +1,4 @@ -//===-- tsan_printf_test.cc -----------------------------------------------===// +//===-- sanitizer_printf_test.cc ------------------------------------------===// // // The LLVM Compiler Infrastructure // @@ -7,16 +7,17 @@ // //===----------------------------------------------------------------------===// // -// This file is a part of ThreadSanitizer (TSan), a race detector. +// Tests for sanitizer_printf.cc // //===----------------------------------------------------------------------===// -#include "tsan_rtl.h" +#include "sanitizer_common/sanitizer_common.h" +#include "sanitizer_common/sanitizer_libc.h" #include "gtest/gtest.h" #include <string.h> #include <limits.h> -namespace __tsan { +namespace __sanitizer { TEST(Printf, Basic) { char buf[1024]; @@ -27,15 +28,21 @@ TEST(Printf, Basic) { (unsigned)10, (unsigned long)11, // NOLINT (void*)0x123, "_string_"); EXPECT_EQ(len, strlen(buf)); - EXPECT_EQ(0, strcmp(buf, "a-1b-2c4294967292e5fahbq" - "0x000000000123e_string_r")); + void *ptr; + if (sizeof(ptr) == 4) { + EXPECT_STREQ("a-1b-2c4294967292e5fahbq" + "0x00000123e_string_r", buf); + } else { + EXPECT_STREQ("a-1b-2c4294967292e5fahbq" + "0x000000000123e_string_r", buf); + } } TEST(Printf, OverflowStr) { char buf[] = "123456789"; uptr len = internal_snprintf(buf, 4, "%s", "abcdef"); // NOLINT EXPECT_EQ(len, (uptr)6); - EXPECT_EQ(0, strcmp(buf, "abc")); + EXPECT_STREQ("abc", buf); EXPECT_EQ(buf[3], 0); EXPECT_EQ(buf[4], '5'); EXPECT_EQ(buf[5], '6'); @@ -48,7 +55,7 @@ TEST(Printf, OverflowStr) { TEST(Printf, OverflowInt) { char buf[] = "123456789"; internal_snprintf(buf, 4, "%d", -123456789); // NOLINT - EXPECT_EQ(0, strcmp(buf, "-12")); + EXPECT_STREQ("-12", buf); EXPECT_EQ(buf[3], 0); EXPECT_EQ(buf[4], '5'); EXPECT_EQ(buf[5], '6'); @@ -60,8 +67,14 @@ TEST(Printf, OverflowInt) { TEST(Printf, OverflowUint) { char buf[] = "123456789"; - internal_snprintf(buf, 4, "a%zx", (unsigned long)0x123456789); // NOLINT - EXPECT_EQ(0, strcmp(buf, "a12")); + uptr val; + if (sizeof(val) == 4) { + val = (uptr)0x12345678; + } else { + val = (uptr)0x123456789ULL; + } + internal_snprintf(buf, 4, "a%zx", val); // NOLINT + EXPECT_STREQ("a12", buf); EXPECT_EQ(buf[3], 0); EXPECT_EQ(buf[4], '5'); EXPECT_EQ(buf[5], '6'); @@ -73,8 +86,14 @@ TEST(Printf, OverflowUint) { TEST(Printf, OverflowPtr) { char buf[] = "123456789"; - internal_snprintf(buf, 4, "%p", (void*)0x123456789); // NOLINT - EXPECT_EQ(0, strcmp(buf, "0x0")); + void *p; + if (sizeof(p) == 4) { + p = (void*)0x1234567; + } else { + p = (void*)0x123456789ULL; + } + internal_snprintf(buf, 4, "%p", p); // NOLINT + EXPECT_STREQ("0x0", buf); EXPECT_EQ(buf[3], 0); EXPECT_EQ(buf[4], '5'); EXPECT_EQ(buf[5], '6'); @@ -91,7 +110,7 @@ static void TestMinMax(const char *fmt, T min, T max) { char buf2[1024]; snprintf(buf2, sizeof(buf2), fmt, min, max); EXPECT_EQ(len, strlen(buf)); - EXPECT_EQ(0, strcmp(buf, buf2)); + EXPECT_STREQ(buf2, buf); } TEST(Printf, MinMax) { @@ -103,4 +122,4 @@ TEST(Printf, MinMax) { TestMinMax<unsigned long>("%zx-%zx", 0, ULONG_MAX); // NOLINT } -} // namespace __tsan +} // namespace __sanitizer diff --git a/lib/sanitizer_common/tests/sanitizer_scanf_interceptor_test.cc b/lib/sanitizer_common/tests/sanitizer_scanf_interceptor_test.cc new file mode 100644 index 000000000000..00b260479da9 --- /dev/null +++ b/lib/sanitizer_common/tests/sanitizer_scanf_interceptor_test.cc @@ -0,0 +1,85 @@ +//===-- sanitizer_scanf_interceptor_test.cc -------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Tests for *scanf interceptors implementation in sanitizer_common. +// +//===----------------------------------------------------------------------===// +#include <vector> + +#include "interception/interception.h" +#include "sanitizer_test_utils.h" +#include "sanitizer_common/sanitizer_libc.h" +#include "gtest/gtest.h" + +using namespace __sanitizer; + +#define COMMON_INTERCEPTOR_WRITE_RANGE(ctx, ptr, size) \ + ((std::vector<unsigned> *)ctx)->push_back(size) + +#include "sanitizer_common/sanitizer_common_interceptors_scanf.inc" + +static void testScanf2(void *ctx, const char *format, ...) { + va_list ap; + va_start(ap, format); + scanf_common(ctx, format, ap); + va_end(ap); +} + +static void testScanf(const char *format, unsigned n, ...) { + std::vector<unsigned> scanf_sizes; + // 16 args should be enough. + testScanf2((void *)&scanf_sizes, format, + (void*)0, (void*)0, (void*)0, (void*)0, + (void*)0, (void*)0, (void*)0, (void*)0, + (void*)0, (void*)0, (void*)0, (void*)0, + (void*)0, (void*)0, (void*)0, (void*)0); + ASSERT_EQ(n, scanf_sizes.size()) << + "Unexpected number of format arguments: '" << format << "'"; + va_list ap; + va_start(ap, n); + for (unsigned i = 0; i < n; ++i) + EXPECT_EQ(va_arg(ap, unsigned), scanf_sizes[i]) << + "Unexpect write size for argument " << i << ", format string '" << + format << "'"; + va_end(ap); +} + +TEST(SanitizerCommonInterceptors, Scanf) { + const unsigned I = sizeof(int); // NOLINT + const unsigned L = sizeof(long); // NOLINT + const unsigned LL = sizeof(long long); // NOLINT + const unsigned S = sizeof(short); // NOLINT + const unsigned C = sizeof(char); // NOLINT + const unsigned D = sizeof(double); // NOLINT + const unsigned F = sizeof(float); // NOLINT + + testScanf("%d", 1, I); + testScanf("%d%d%d", 3, I, I, I); + testScanf("ab%u%dc", 2, I, I); + testScanf("%ld", 1, L); + testScanf("%llu", 1, LL); + testScanf("a %hd%hhx", 2, S, C); + + testScanf("%%", 0); + testScanf("a%%", 0); + testScanf("a%%b", 0); + testScanf("a%%%%b", 0); + testScanf("a%%b%%", 0); + testScanf("a%%%%%%b", 0); + testScanf("a%%%%%b", 0); + testScanf("a%%%%%f", 1, F); + testScanf("a%%%lxb", 1, L); + testScanf("a%lf%%%lxb", 2, D, L); + testScanf("%nf", 1, I); + + testScanf("%10s", 1, 11); + testScanf("%%10s", 0); + testScanf("%*10s", 0); + testScanf("%*d", 0); +} diff --git a/lib/sanitizer_common/tests/sanitizer_stackdepot_test.cc b/lib/sanitizer_common/tests/sanitizer_stackdepot_test.cc new file mode 100644 index 000000000000..5350c2ab8dbc --- /dev/null +++ b/lib/sanitizer_common/tests/sanitizer_stackdepot_test.cc @@ -0,0 +1,69 @@ +//===-- sanitizer_stackdepot_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. +// +//===----------------------------------------------------------------------===// +#include "sanitizer_common/sanitizer_stackdepot.h" +#include "sanitizer_common/sanitizer_internal_defs.h" +#include "sanitizer_common/sanitizer_libc.h" +#include "gtest/gtest.h" + +namespace __sanitizer { + +TEST(SanitizerCommon, StackDepotBasic) { + uptr s1[] = {1, 2, 3, 4, 5}; + u32 i1 = StackDepotPut(s1, ARRAY_SIZE(s1)); + uptr sz1 = 0; + const uptr *sp1 = StackDepotGet(i1, &sz1); + EXPECT_NE(sp1, (uptr*)0); + EXPECT_EQ(sz1, ARRAY_SIZE(s1)); + EXPECT_EQ(internal_memcmp(sp1, s1, sizeof(s1)), 0); +} + +TEST(SanitizerCommon, StackDepotAbsent) { + uptr sz1 = 0; + const uptr *sp1 = StackDepotGet((1 << 30) - 1, &sz1); + EXPECT_EQ(sp1, (uptr*)0); +} + +TEST(SanitizerCommon, StackDepotEmptyStack) { + u32 i1 = StackDepotPut(0, 0); + uptr sz1 = 0; + const uptr *sp1 = StackDepotGet(i1, &sz1); + EXPECT_EQ(sp1, (uptr*)0); +} + +TEST(SanitizerCommon, StackDepotZeroId) { + uptr sz1 = 0; + const uptr *sp1 = StackDepotGet(0, &sz1); + EXPECT_EQ(sp1, (uptr*)0); +} + +TEST(SanitizerCommon, StackDepotSame) { + uptr s1[] = {1, 2, 3, 4, 6}; + u32 i1 = StackDepotPut(s1, ARRAY_SIZE(s1)); + u32 i2 = StackDepotPut(s1, ARRAY_SIZE(s1)); + EXPECT_EQ(i1, i2); + uptr sz1 = 0; + const uptr *sp1 = StackDepotGet(i1, &sz1); + EXPECT_NE(sp1, (uptr*)0); + EXPECT_EQ(sz1, ARRAY_SIZE(s1)); + EXPECT_EQ(internal_memcmp(sp1, s1, sizeof(s1)), 0); +} + +TEST(SanitizerCommon, StackDepotSeveral) { + uptr s1[] = {1, 2, 3, 4, 7}; + u32 i1 = StackDepotPut(s1, ARRAY_SIZE(s1)); + uptr s2[] = {1, 2, 3, 4, 8, 9}; + u32 i2 = StackDepotPut(s2, ARRAY_SIZE(s2)); + EXPECT_NE(i1, i2); +} + +} // namespace __sanitizer diff --git a/lib/sanitizer_common/tests/sanitizer_test_main.cc b/lib/sanitizer_common/tests/sanitizer_test_main.cc new file mode 100644 index 000000000000..12d1d15af917 --- /dev/null +++ b/lib/sanitizer_common/tests/sanitizer_test_main.cc @@ -0,0 +1,19 @@ +//===-- sanitizer_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 ThreadSanitizer/AddressSanitizer runtime. +// +//===----------------------------------------------------------------------===// +#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(); +} diff --git a/lib/sanitizer_common/tests/sanitizer_test_utils.h b/lib/sanitizer_common/tests/sanitizer_test_utils.h new file mode 100644 index 000000000000..6129ea8a5370 --- /dev/null +++ b/lib/sanitizer_common/tests/sanitizer_test_utils.h @@ -0,0 +1,80 @@ +//===-- sanitizer_test_utils.h ----------------------------------*- C++ -*-===// +// +// 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 *Sanitizer runtime. +// Common unit tests utilities. +// +//===----------------------------------------------------------------------===// + +#ifndef SANITIZER_TEST_UTILS_H +#define SANITIZER_TEST_UTILS_H + +#if defined(_WIN32) +typedef unsigned __int8 uint8_t; +typedef unsigned __int16 uint16_t; +typedef unsigned __int32 uint32_t; +typedef unsigned __int64 uint64_t; +typedef __int8 int8_t; +typedef __int16 int16_t; +typedef __int32 int32_t; +typedef __int64 int64_t; +# define NOINLINE __declspec(noinline) +# define USED +#else // defined(_WIN32) +# define NOINLINE __attribute__((noinline)) +# define USED __attribute__((used)) +#include <stdint.h> +#endif // defined(_WIN32) + +#if !defined(__has_feature) +#define __has_feature(x) 0 +#endif + +#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) +# define ATTRIBUTE_NO_ADDRESS_SAFETY_ANALYSIS \ + __attribute__((no_address_safety_analysis)) +#else +# define ATTRIBUTE_NO_ADDRESS_SAFETY_ANALYSIS +#endif + +#if __LP64__ || defined(_WIN64) +# define SANITIZER_WORDSIZE 64 +#else +# define SANITIZER_WORDSIZE 32 +#endif + +// Make the compiler thinks that something is going on there. +inline void break_optimization(void *arg) { + __asm__ __volatile__("" : : "r" (arg) : "memory"); +} + +// This function returns its parameter but in such a way that compiler +// can not prove it. +template<class T> +NOINLINE +static T Ident(T t) { + T ret = t; + break_optimization(&ret); + return ret; +} + +// Simple stand-alone pseudorandom number generator. +// Current algorithm is ANSI C linear congruential PRNG. +static inline uint32_t my_rand_r(uint32_t* state) { + return (*state = *state * 1103515245 + 12345) >> 16; +} + +static uint32_t global_seed = 0; + +static inline uint32_t my_rand() { + return my_rand_r(&global_seed); +} + + +#endif // SANITIZER_TEST_UTILS_H diff --git a/lib/sanitizer_common/tests/standalone_malloc_test.cc b/lib/sanitizer_common/tests/standalone_malloc_test.cc new file mode 100644 index 000000000000..9e6f7c93b04b --- /dev/null +++ b/lib/sanitizer_common/tests/standalone_malloc_test.cc @@ -0,0 +1,87 @@ +#include <stdio.h> +#include <vector> +#include <pthread.h> +#include <malloc.h> +#include <algorithm> + +using namespace std; + +const size_t kNumThreds = 16; +const size_t kNumIters = 1 << 23; + +inline void break_optimization(void *arg) { + __asm__ __volatile__("" : : "r" (arg) : "memory"); +} + +__attribute__((noinline)) +static void *MallocThread(void *t) { + size_t total_malloced = 0, total_freed = 0; + size_t max_in_use = 0; + size_t tid = reinterpret_cast<size_t>(t); + vector<pair<char *, size_t> > allocated; + allocated.reserve(kNumIters); + for (size_t i = 1; i < kNumIters; i++) { + if ((i % (kNumIters / 4)) == 0 && tid == 0) + fprintf(stderr, " T[%ld] iter %ld\n", tid, i); + bool allocate = (i % 5) <= 2; // 60% malloc, 40% free + if (i > kNumIters / 4) + allocate = i % 2; // then switch to 50% malloc, 50% free + if (allocate) { + size_t size = 1 + (i % 200); + if ((i % 10001) == 0) + size *= 4096; + total_malloced += size; + char *x = new char[size]; + x[0] = x[size - 1] = x[size / 2] = 0; + allocated.push_back(make_pair(x, size)); + max_in_use = max(max_in_use, total_malloced - total_freed); + } else { + if (allocated.empty()) continue; + size_t slot = i % allocated.size(); + char *p = allocated[slot].first; + p[0] = 0; // emulate last user touch of the block + size_t size = allocated[slot].second; + total_freed += size; + swap(allocated[slot], allocated.back()); + allocated.pop_back(); + delete [] p; + } + } + if (tid == 0) + fprintf(stderr, " T[%ld] total_malloced: %ldM in use %ldM max %ldM\n", + tid, total_malloced >> 20, (total_malloced - total_freed) >> 20, + max_in_use >> 20); + for (size_t i = 0; i < allocated.size(); i++) + delete [] allocated[i].first; + return 0; +} + +template <int depth> +struct DeepStack { + __attribute__((noinline)) + static void *run(void *t) { + break_optimization(0); + DeepStack<depth - 1>::run(t); + break_optimization(0); + return 0; + } +}; + +template<> +struct DeepStack<0> { + static void *run(void *t) { + MallocThread(t); + return 0; + } +}; + +// Build with -Dstandalone_malloc_test=main to make it a separate program. +int standalone_malloc_test() { + pthread_t t[kNumThreds]; + for (size_t i = 0; i < kNumThreds; i++) + pthread_create(&t[i], 0, DeepStack<200>::run, reinterpret_cast<void *>(i)); + for (size_t i = 0; i < kNumThreds; i++) + pthread_join(t[i], 0); + malloc_stats(); + return 0; +} diff --git a/lib/tsan/CMakeLists.txt b/lib/tsan/CMakeLists.txt index acfb854d2606..34e3a2ea524e 100644 --- a/lib/tsan/CMakeLists.txt +++ b/lib/tsan/CMakeLists.txt @@ -1,8 +1,22 @@ -# Build for the AddressSanitizer runtime support library. +# Build for the ThreadSanitizer runtime support library. -file(GLOB TSAN_SOURCES "*.cc") +include_directories(..) -if(CAN_TARGET_X86_64) - add_library(clang_rt.tsan-x86_64 STATIC ${TSAN_SOURCES}) - set_target_properties(clang_rt.tsan-x86_64 PROPERTIES COMPILE_FLAGS "${TARGET_X86_64_CFLAGS}") +set(TSAN_CFLAGS ${SANITIZER_COMMON_CFLAGS}) +# FIXME: Add support for compile flags: +# -Wframe-larger-than=512, +# -Wglobal-constructors, +# --sysroot=. + +if("${CMAKE_BUILD_TYPE}" EQUAL "Release") + set(TSAN_COMMON_DEFINITIONS DEBUG=0) +else() + set(TSAN_COMMON_DEFINITIONS DEBUG=1) +endif() + +add_subdirectory(rtl) + +if(LLVM_INCLUDE_TESTS) + add_subdirectory(tests) endif() +add_subdirectory(lit_tests) diff --git a/lib/tsan/Makefile.old b/lib/tsan/Makefile.old index 2091f61335e9..593482fbb5da 100644 --- a/lib/tsan/Makefile.old +++ b/lib/tsan/Makefile.old @@ -1,6 +1,6 @@ DEBUG=0 LDFLAGS=-ldl -lpthread -pie -CXXFLAGS = -fPIE -g -Wall -Werror -DTSAN_DEBUG=$(DEBUG) +CXXFLAGS = -fPIE -g -Wall -Werror -DTSAN_DEBUG=$(DEBUG) -DSANITIZER_DEBUG=$(DEBUG) # Silence warnings that Clang produces for gtest code. # Use -Wno-attributes so that gcc doesn't complain about unknown warning types. CXXFLAGS += -Wno-attributes @@ -8,24 +8,25 @@ ifeq ($(DEBUG), 0) CXXFLAGS += -O3 endif ifeq ($(CXX), clang++) - CXXFLAGS+= -Wno-unused-private-field -Wno-static-in-inline + CXXFLAGS+= -Wno-unused-private-field -Wno-static-in-inline -Wgnu endif LIBTSAN=rtl/libtsan.a GTEST_ROOT=third_party/googletest GTEST_INCLUDE=-I$(GTEST_ROOT)/include GTEST_BUILD_DIR=$(GTEST_ROOT)/build -GTEST_LIB=$(GTEST_BUILD_DIR)/gtest-all.o +GTEST_LIB_NAME=gtest-all.o +GTEST_LIB=$(GTEST_BUILD_DIR)/$(GTEST_LIB_NAME) SANITIZER_COMMON_TESTS_SRC=$(wildcard ../sanitizer_common/tests/*_test.cc) SANITIZER_COMMON_TESTS_OBJ=$(patsubst %.cc,%.o,$(SANITIZER_COMMON_TESTS_SRC)) -RTL_TEST_SRC=$(wildcard rtl_tests/*.cc) +RTL_TEST_SRC=$(wildcard tests/rtl/*.cc) RTL_TEST_OBJ=$(patsubst %.cc,%.o,$(RTL_TEST_SRC)) -UNIT_TEST_SRC=$(wildcard unit_tests/*_test.cc) +UNIT_TEST_SRC=$(wildcard tests/unit/*_test.cc) UNIT_TEST_OBJ=$(patsubst %.cc,%.o,$(UNIT_TEST_SRC)) UNIT_TEST_HDR=$(wildcard rtl/*.h) $(wildcard ../sanitizer_common/*.h) -INCLUDES=-Irtl -I.. $(GTEST_INCLUDE) +INCLUDES=-Irtl -I.. -I../../include $(GTEST_INCLUDE) all: libtsan test @@ -34,9 +35,8 @@ help: @ echo "The most useful targets are:" @ echo " make install_deps # Install third-party dependencies required for building" @ echo " make presubmit # Run it every time before committing" - @ echo " make lint # Run the style checker" @ echo - @ echo "For more info, see http://code.google.com/p/data-race-test/wiki/ThreadSanitizer2" + @ echo "For more info, see http://code.google.com/p/thread-sanitizer/wiki/Development" $(LIBTSAN): libtsan @@ -54,10 +54,10 @@ test: libtsan tsan_test run: all (ulimit -s 8192; ./tsan_test) - ./output_tests/test_output.sh + ./lit_tests/test_output.sh presubmit: - $(MAKE) -f Makefile.old lint -j 4 + ../sanitizer_common/scripts/check_lint.sh # Debug build with clang. $(MAKE) -f Makefile.old clean $(MAKE) -f Makefile.old run DEBUG=1 -j 16 CC=clang CXX=clang++ @@ -71,34 +71,23 @@ presubmit: $(MAKE) -f Makefile.old clean $(MAKE) -f Makefile.old run DEBUG=0 -j 16 CC=gcc CXX=g++ ./check_analyze.sh + # Sanity check for Go runtime + (cd go && ./buildgo.sh) + # Check cmake build + ./check_cmake.sh @ echo PRESUBMIT PASSED -RTL_LINT_FITLER=-legal/copyright,-build/include,-readability/casting,-build/header_guard,-build/namespaces - -lint: lint_tsan lint_tests -lint_tsan: - third_party/cpplint/cpplint.py --filter=$(RTL_LINT_FITLER) rtl/*.{cc,h} \ - ../sanitizer_common/*.{cc,h} -lint_tests: - third_party/cpplint/cpplint.py --filter=$(RTL_LINT_FITLER) \ - rtl_tests/*.{cc,h} unit_tests/*.cc ../sanitizer_common/tests/*.cc - install_deps: rm -rf third_party mkdir third_party (cd third_party && \ - svn co -r613 http://googletest.googlecode.com/svn/trunk googletest && \ - svn co -r82 http://google-styleguide.googlecode.com/svn/trunk/cpplint cpplint \ + svn co -r613 http://googletest.googlecode.com/svn/trunk googletest \ ) -# Remove verbose printf from lint. Not strictly necessary. -hack_cpplint: - sed -i "s/ sys.stderr.write('Done processing.*//g" third_party/cpplint/cpplint.py - $(GTEST_LIB): mkdir -p $(GTEST_BUILD_DIR) && \ cd $(GTEST_BUILD_DIR) && \ - $(MAKE) -f ../make/Makefile CXXFLAGS="$(CXXFLAGS)" CFLAGS="$(CFLAGS)" CC=$(CC) CXX=$(CXX) + $(MAKE) -f ../make/Makefile CXXFLAGS="$(CXXFLAGS)" CFLAGS="$(CFLAGS)" CC=$(CC) CXX=$(CXX) $(GTEST_LIB_NAME) clean: rm -f asm_*.s libtsan.nm libtsan.objdump */*.o tsan_test diff --git a/lib/tsan/check_cmake.sh b/lib/tsan/check_cmake.sh new file mode 100755 index 000000000000..5f11e727f091 --- /dev/null +++ b/lib/tsan/check_cmake.sh @@ -0,0 +1,11 @@ +#!/bin/bash +set -u +set -e + +ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +mkdir -p $ROOT/build +cd $ROOT/build +CC=clang CXX=clang++ cmake -DLLVM_ENABLE_WERROR=ON -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=ON $ROOT/../../../.. +make -j64 +make check-tsan check-sanitizer -j64 + diff --git a/lib/tsan/go/buildgo.sh b/lib/tsan/go/buildgo.sh index a0d2f6761b41..a153afd6ee8e 100755 --- a/lib/tsan/go/buildgo.sh +++ b/lib/tsan/go/buildgo.sh @@ -1,24 +1,12 @@ #!/bin/bash set -e -if [ "`uname -a | grep Linux`" != "" ]; then - LINUX=1 - SUFFIX="linux_amd64" -elif [ "`uname -a | grep Darwin`" != "" ]; then - MAC=1 - SUFFIX="darwin_amd64" -else - echo Unknown platform - exit 1 -fi - SRCS=" tsan_go.cc ../rtl/tsan_clock.cc ../rtl/tsan_flags.cc ../rtl/tsan_md5.cc ../rtl/tsan_mutex.cc - ../rtl/tsan_printf.cc ../rtl/tsan_report.cc ../rtl/tsan_rtl.cc ../rtl/tsan_rtl_mutex.cc @@ -31,48 +19,59 @@ SRCS=" ../../sanitizer_common/sanitizer_common.cc ../../sanitizer_common/sanitizer_flags.cc ../../sanitizer_common/sanitizer_libc.cc - ../../sanitizer_common/sanitizer_posix.cc ../../sanitizer_common/sanitizer_printf.cc - ../../sanitizer_common/sanitizer_symbolizer.cc " -if [ "$LINUX" != "" ]; then +if [ "`uname -a | grep Linux`" != "" ]; then + SUFFIX="linux_amd64" + OSCFLAGS="-fPIC -ffreestanding" + OSLDFLAGS="-lpthread -fPIC -fpie" SRCS+=" ../rtl/tsan_platform_linux.cc + ../../sanitizer_common/sanitizer_posix.cc ../../sanitizer_common/sanitizer_linux.cc " -elif [ "$MAC" != "" ]; then - SRCS+=" - ../rtl/tsan_platform_mac.cc - ../../sanitizer_common/sanitizer_mac.cc - " +elif [ "`uname -a | grep Darwin`" != "" ]; then + SUFFIX="darwin_amd64" + OSCFLAGS="-fPIC" + OSLDFLAGS="-lpthread -fPIC -fpie" + SRCS+=" + ../rtl/tsan_platform_mac.cc + ../../sanitizer_common/sanitizer_posix.cc + ../../sanitizer_common/sanitizer_mac.cc + " +elif [ "`uname -a | grep MINGW`" != "" ]; then + SUFFIX="windows_amd64" + OSCFLAGS="-Wno-error=attributes -Wno-attributes" + OSLDFLAGS="" + SRCS+=" + ../rtl/tsan_platform_windows.cc + ../../sanitizer_common/sanitizer_win.cc + " +else + echo Unknown platform + exit 1 fi SRCS+=$ADD_SRCS -#ASMS="../rtl/tsan_rtl_amd64.S" rm -f gotsan.cc for F in $SRCS; do cat $F >> gotsan.cc done -FLAGS=" -I../rtl -I../.. -I../../sanitizer_common -fPIC -g -Wall -Werror -fno-exceptions -DTSAN_GO -DSANITIZER_GO -DTSAN_SHADOW_COUNT=4" +FLAGS=" -I../rtl -I../.. -I../../sanitizer_common -I../../../include -m64 -Wall -Werror -fno-exceptions -DTSAN_GO -DSANITIZER_GO -DTSAN_SHADOW_COUNT=4 $OSCFLAGS" if [ "$DEBUG" == "" ]; then FLAGS+=" -DTSAN_DEBUG=0 -O3 -fomit-frame-pointer" else FLAGS+=" -DTSAN_DEBUG=1 -g" fi -if [ "$LINUX" != "" ]; then - FLAGS+=" -ffreestanding" -fi - echo gcc gotsan.cc -S -o tmp.s $FLAGS $CFLAGS gcc gotsan.cc -S -o tmp.s $FLAGS $CFLAGS cat tmp.s $ASMS > gotsan.s echo as gotsan.s -o race_$SUFFIX.syso as gotsan.s -o race_$SUFFIX.syso -gcc test.c race_$SUFFIX.syso -lpthread -o test -TSAN_OPTIONS="exitcode=0" ./test - +gcc test.c race_$SUFFIX.syso -m64 -o test $OSLDFLAGS +GORACE="exitcode=0 atexit_sleep_ms=0" ./test diff --git a/lib/tsan/go/test.c b/lib/tsan/go/test.c index a9a5b3dbfcad..2414a1e9925f 100644 --- a/lib/tsan/go/test.c +++ b/lib/tsan/go/test.c @@ -15,6 +15,7 @@ void __tsan_init(); void __tsan_fini(); +void __tsan_map_shadow(void *addr, unsigned long size); void __tsan_go_start(int pgoid, int chgoid, void *pc); void __tsan_go_end(int goid); void __tsan_read(int goid, void *addr, void *pc); @@ -35,6 +36,7 @@ char buf[10]; int main(void) { __tsan_init(); + __tsan_map_shadow(buf, sizeof(buf) + 4096); __tsan_func_enter(0, &main); __tsan_malloc(0, buf, 10, 0); __tsan_release(0, buf); diff --git a/lib/tsan/go/tsan_go.cc b/lib/tsan/go/tsan_go.cc index 4b3076c46ce7..360608a0cf1b 100644 --- a/lib/tsan/go/tsan_go.cc +++ b/lib/tsan/go/tsan_go.cc @@ -18,7 +18,9 @@ namespace __tsan { -static ThreadState *goroutines[kMaxTid]; +const int kMaxGoroutinesEver = 128*1024; + +static ThreadState *goroutines[kMaxGoroutinesEver]; void InitializeInterceptors() { } @@ -33,7 +35,7 @@ bool IsExpectedReport(uptr addr, uptr size) { void internal_start_thread(void(*func)(void*), void *arg) { } -ReportStack *SymbolizeData(uptr addr) { +ReportLocation *SymbolizeData(uptr addr) { return 0; } @@ -79,9 +81,14 @@ ReportStack *SymbolizeCode(uptr addr) { extern "C" { static void AllocGoroutine(int tid) { - goroutines[tid] = (ThreadState*)internal_alloc(MBlockThreadContex, + if (tid >= kMaxGoroutinesEver) { + Printf("FATAL: Reached goroutine limit\n"); + Die(); + } + ThreadState *thr = (ThreadState*)internal_alloc(MBlockThreadContex, sizeof(ThreadState)); - internal_memset(goroutines[tid], 0, sizeof(ThreadState)); + internal_memset(thr, 0, sizeof(*thr)); + goroutines[tid] = thr; } void __tsan_init() { @@ -98,7 +105,11 @@ void __tsan_fini() { thr->in_rtl++; int res = Finalize(thr); thr->in_rtl--; - exit(res); + exit(res); +} + +void __tsan_map_shadow(uptr addr, uptr size) { + MapShadow(addr, size); } void __tsan_read(int goid, void *addr, void *pc) { @@ -111,6 +122,18 @@ void __tsan_write(int goid, void *addr, void *pc) { MemoryAccess(thr, (uptr)pc, (uptr)addr, 0, true); } +void __tsan_read_range(int goid, void *addr, uptr size, uptr step, void *pc) { + ThreadState *thr = goroutines[goid]; + for (uptr i = 0; i < size; i += step) + MemoryAccess(thr, (uptr)pc, (uptr)addr + i, 0, false); +} + +void __tsan_write_range(int goid, void *addr, uptr size, uptr step, void *pc) { + ThreadState *thr = goroutines[goid]; + for (uptr i = 0; i < size; i += step) + MemoryAccess(thr, (uptr)pc, (uptr)addr + i, 0, true); +} + void __tsan_func_enter(int goid, void *pc) { ThreadState *thr = goroutines[goid]; FuncEntry(thr, (uptr)pc); @@ -123,9 +146,10 @@ void __tsan_func_exit(int goid) { void __tsan_malloc(int goid, void *p, uptr sz, void *pc) { ThreadState *thr = goroutines[goid]; + if (thr == 0) // probably before __tsan_init() + return; thr->in_rtl++; - MemoryResetRange(thr, (uptr)pc, (uptr)p, sz); - MemoryAccessRange(thr, (uptr)pc, (uptr)p, sz, true); + MemoryRangeImitateWrite(thr, (uptr)pc, (uptr)p, sz); thr->in_rtl--; } @@ -142,7 +166,7 @@ void __tsan_go_start(int pgoid, int chgoid, void *pc) { thr->in_rtl++; parent->in_rtl++; int goid2 = ThreadCreate(parent, (uptr)pc, 0, true); - ThreadStart(thr, goid2); + ThreadStart(thr, goid2, 0); parent->in_rtl--; thr->in_rtl--; } @@ -152,6 +176,8 @@ void __tsan_go_end(int goid) { thr->in_rtl++; ThreadFinish(thr); thr->in_rtl--; + internal_free(thr); + goroutines[goid] = 0; } void __tsan_acquire(int goid, void *addr) { @@ -159,7 +185,6 @@ void __tsan_acquire(int goid, void *addr) { thr->in_rtl++; Acquire(thr, 0, (uptr)addr); thr->in_rtl--; - //internal_free(thr); } void __tsan_release(int goid, void *addr) { @@ -178,8 +203,42 @@ void __tsan_release_merge(int goid, void *addr) { void __tsan_finalizer_goroutine(int goid) { ThreadState *thr = goroutines[goid]; - ThreadFinalizerGoroutine(thr); -} + AcquireGlobal(thr, 0); +} + +#ifdef _WIN32 +// MinGW gcc emits calls to the function. +void ___chkstk_ms(void) { +// The implementation must be along the lines of: +// .code64 +// PUBLIC ___chkstk_ms +// //cfi_startproc() +// ___chkstk_ms: +// push rcx +// //cfi_push(%rcx) +// push rax +// //cfi_push(%rax) +// cmp rax, PAGE_SIZE +// lea rcx, [rsp + 24] +// jb l_LessThanAPage +// .l_MoreThanAPage: +// sub rcx, PAGE_SIZE +// or rcx, 0 +// sub rax, PAGE_SIZE +// cmp rax, PAGE_SIZE +// ja l_MoreThanAPage +// .l_LessThanAPage: +// sub rcx, rax +// or [rcx], 0 +// pop rax +// //cfi_pop(%rax) +// pop rcx +// //cfi_pop(%rcx) +// ret +// //cfi_endproc() +// END +} +#endif } // extern "C" } // namespace __tsan diff --git a/lib/tsan/lit_tests/CMakeLists.txt b/lib/tsan/lit_tests/CMakeLists.txt new file mode 100644 index 000000000000..ff2508dd75af --- /dev/null +++ b/lib/tsan/lit_tests/CMakeLists.txt @@ -0,0 +1,36 @@ +configure_lit_site_cfg( + ${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.in + ${CMAKE_CURRENT_BINARY_DIR}/lit.site.cfg + ) + +configure_lit_site_cfg( + ${CMAKE_CURRENT_SOURCE_DIR}/Unit/lit.site.cfg.in + ${CMAKE_CURRENT_BINARY_DIR}/Unit/lit.site.cfg + ) + +if(COMPILER_RT_CAN_EXECUTE_TESTS) + # Run TSan output tests only if we're sure we can produce working binaries. + set(TSAN_TEST_DEPS + clang clang-headers FileCheck count not llvm-symbolizer + ${TSAN_RUNTIME_LIBRARIES} + ) + set(TSAN_TEST_PARAMS + tsan_site_config=${CMAKE_CURRENT_BINARY_DIR}/lit.site.cfg + ) + if(LLVM_INCLUDE_TESTS) + list(APPEND TSAN_TEST_DEPS TsanUnitTests) + endif() + add_lit_testsuite(check-tsan "Running ThreadSanitizer tests" + ${CMAKE_CURRENT_BINARY_DIR} + PARAMS ${TSAN_TEST_PARAMS} + DEPENDS ${TSAN_TEST_DEPS} + ) + set_target_properties(check-tsan PROPERTIES FOLDER "TSan unittests") +elseif(LLVM_INCLUDE_TESTS) + # Otherwise run only TSan unit tests (they are linked using the + # host compiler). + add_lit_testsuite(check-tsan "Running ThreadSanitizer tests" + ${CMAKE_CURRENT_BINARY_DIR}/Unit + DEPENDS TsanUnitTests llvm-symbolizer) + set_target_properties(check-tsan PROPERTIES FOLDER "TSan unittests") +endif() diff --git a/lib/tsan/lit_tests/Helpers/blacklist.txt b/lib/tsan/lit_tests/Helpers/blacklist.txt new file mode 100644 index 000000000000..22225e542ff3 --- /dev/null +++ b/lib/tsan/lit_tests/Helpers/blacklist.txt @@ -0,0 +1 @@ +fun:*Blacklisted_Thread2* diff --git a/lib/tsan/lit_tests/Helpers/lit.local.cfg b/lib/tsan/lit_tests/Helpers/lit.local.cfg new file mode 100644 index 000000000000..9246b10352a7 --- /dev/null +++ b/lib/tsan/lit_tests/Helpers/lit.local.cfg @@ -0,0 +1,2 @@ +# Files in this directory are helper files for other output tests. +config.suffixes = [] diff --git a/lib/tsan/lit_tests/Unit/lit.cfg b/lib/tsan/lit_tests/Unit/lit.cfg new file mode 100644 index 000000000000..6688697c0c1b --- /dev/null +++ b/lib/tsan/lit_tests/Unit/lit.cfg @@ -0,0 +1,37 @@ +# -*- Python -*- + +import os + +def get_required_attr(config, attr_name): + attr_value = getattr(config, attr_name, None) + if not attr_value: + lit.fatal("No attribute %r in test configuration! You may need to run " + "tests from your build directory or add this attribute " + "to lit.site.cfg " % attr_name) + return attr_value + +# Setup attributes common for all compiler-rt projects. +llvm_src_root = get_required_attr(config, 'llvm_src_root') +compiler_rt_lit_unit_cfg = os.path.join(llvm_src_root, "projects", + "compiler-rt", "lib", + "lit.common.unit.cfg") +lit.load_config(config, compiler_rt_lit_unit_cfg) + +# Setup config name. +config.name = 'ThreadSanitizer-Unit' + +# Setup test source and exec root. For unit tests, we define +# it as build directory with TSan unit tests. +llvm_obj_root = get_required_attr(config, "llvm_obj_root") +config.test_exec_root = os.path.join(llvm_obj_root, "projects", + "compiler-rt", "lib", + "tsan", "tests") +config.test_source_root = config.test_exec_root + +# Get path to external LLVM symbolizer to run ThreadSanitizer unit tests. +llvm_tools_dir = getattr(config, 'llvm_tools_dir', None) +if llvm_tools_dir: + llvm_symbolizer_path = os.path.join(llvm_tools_dir, "llvm-symbolizer") + config.environment['TSAN_OPTIONS'] = ("external_symbolizer_path=" + + llvm_symbolizer_path) + diff --git a/lib/tsan/lit_tests/Unit/lit.site.cfg.in b/lib/tsan/lit_tests/Unit/lit.site.cfg.in new file mode 100644 index 000000000000..23654b9be2ee --- /dev/null +++ b/lib/tsan/lit_tests/Unit/lit.site.cfg.in @@ -0,0 +1,18 @@ +## Autogenerated by LLVM/Clang configuration. +# Do not edit! + +config.build_type = "@CMAKE_BUILD_TYPE@" +config.llvm_obj_root = "@LLVM_BINARY_DIR@" +config.llvm_src_root = "@LLVM_SOURCE_DIR@" +config.llvm_tools_dir = "@LLVM_TOOLS_DIR@" + +# LLVM tools dir can be passed in lit parameters, so try to +# apply substitution. +try: + config.llvm_tools_dir = config.llvm_tools_dir % lit.params +except KeyError,e: + key, = e.args + lit.fatal("unable to find %r parameter, use '--param=%s=VALUE'" % (key, key)) + +# Let the main config do the real work. +lit.load_config(config, "@CMAKE_CURRENT_SOURCE_DIR@/Unit/lit.cfg") diff --git a/lib/tsan/lit_tests/blacklist.cc b/lib/tsan/lit_tests/blacklist.cc new file mode 100644 index 000000000000..5baf926e6272 --- /dev/null +++ b/lib/tsan/lit_tests/blacklist.cc @@ -0,0 +1,31 @@ +// Test blacklist functionality for TSan. + +// RUN: %clangxx_tsan -O1 %s \ +// RUN: -fsanitize-blacklist=%p/Helpers/blacklist.txt \ +// RUN: -o %t && %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> + +int Global; + +void *Thread1(void *x) { + Global++; + return NULL; +} + +void *Blacklisted_Thread2(void *x) { + Global--; + return NULL; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Blacklisted_Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + printf("PASS\n"); + return 0; +} + +// CHECK-NOT: ThreadSanitizer: data race diff --git a/lib/tsan/lit_tests/fd_close_norace.cc b/lib/tsan/lit_tests/fd_close_norace.cc new file mode 100644 index 000000000000..a8b1a6d7b9e2 --- /dev/null +++ b/lib/tsan/lit_tests/fd_close_norace.cc @@ -0,0 +1,33 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +void *Thread1(void *x) { + int f = open("/dev/random", O_RDONLY); + close(f); + return NULL; +} + +void *Thread2(void *x) { + sleep(1); + int f = open("/dev/random", O_RDONLY); + close(f); + return NULL; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + printf("OK\n"); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race + + diff --git a/lib/tsan/lit_tests/fd_dup_norace.cc b/lib/tsan/lit_tests/fd_dup_norace.cc new file mode 100644 index 000000000000..8826f90fc485 --- /dev/null +++ b/lib/tsan/lit_tests/fd_dup_norace.cc @@ -0,0 +1,34 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +int fds[2]; + +void *Thread1(void *x) { + char buf; + read(fds[0], &buf, 1); + close(fds[0]); + return 0; +} + +void *Thread2(void *x) { + close(fds[1]); + return 0; +} + +int main() { + fds[0] = open("/dev/random", O_RDONLY); + fds[1] = dup2(fds[0], 100); + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + printf("OK\n"); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race diff --git a/lib/tsan/lit_tests/fd_location.cc b/lib/tsan/lit_tests/fd_location.cc new file mode 100644 index 000000000000..35f9aabb0377 --- /dev/null +++ b/lib/tsan/lit_tests/fd_location.cc @@ -0,0 +1,33 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +int fds[2]; + +void *Thread1(void *x) { + write(fds[1], "a", 1); + return NULL; +} + +void *Thread2(void *x) { + sleep(1); + close(fds[0]); + close(fds[1]); + return NULL; +} + +int main() { + pipe(fds); + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: Location is file descriptor {{[0-9]+}} created by main thread at: +// CHECK: #0 pipe +// CHECK: #1 main + diff --git a/lib/tsan/lit_tests/fd_pipe_norace.cc b/lib/tsan/lit_tests/fd_pipe_norace.cc new file mode 100644 index 000000000000..2da69ea21112 --- /dev/null +++ b/lib/tsan/lit_tests/fd_pipe_norace.cc @@ -0,0 +1,33 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +int fds[2]; +int X; + +void *Thread1(void *x) { + X = 42; + write(fds[1], "a", 1); + return NULL; +} + +void *Thread2(void *x) { + char buf; + while (read(fds[0], &buf, 1) != 1) { + } + X = 43; + return NULL; +} + +int main() { + pipe(fds); + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + printf("OK\n"); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race diff --git a/lib/tsan/lit_tests/fd_pipe_race.cc b/lib/tsan/lit_tests/fd_pipe_race.cc new file mode 100644 index 000000000000..dfdb7795aae6 --- /dev/null +++ b/lib/tsan/lit_tests/fd_pipe_race.cc @@ -0,0 +1,37 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +int fds[2]; + +void *Thread1(void *x) { + write(fds[1], "a", 1); + return NULL; +} + +void *Thread2(void *x) { + sleep(1); + close(fds[0]); + close(fds[1]); + return NULL; +} + +int main() { + pipe(fds); + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: Write of size 8 +// CHECK: #0 close +// CHECK: #1 Thread2 +// CHECK: Previous read of size 8 +// CHECK: #0 write +// CHECK: #1 Thread1 + + diff --git a/lib/tsan/lit_tests/fd_socket_connect_norace.cc b/lib/tsan/lit_tests/fd_socket_connect_norace.cc new file mode 100644 index 000000000000..065299a9c6b6 --- /dev/null +++ b/lib/tsan/lit_tests/fd_socket_connect_norace.cc @@ -0,0 +1,45 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +struct sockaddr_in addr; +int X; + +void *ClientThread(void *x) { + int c = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + X = 42; + if (connect(c, (struct sockaddr*)&addr, sizeof(addr))) { + perror("connect"); + exit(1); + } + close(c); + return NULL; +} + +int main() { + int s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + addr.sin_family = AF_INET; + inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr); + addr.sin_port = INADDR_ANY; + socklen_t len = sizeof(addr); + bind(s, (sockaddr*)&addr, len); + getsockname(s, (sockaddr*)&addr, &len); + listen(s, 10); + pthread_t t; + pthread_create(&t, 0, ClientThread, 0); + int c = accept(s, 0, 0); + X = 42; + pthread_join(t, 0); + close(c); + close(s); + printf("OK\n"); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race + diff --git a/lib/tsan/lit_tests/fd_socket_norace.cc b/lib/tsan/lit_tests/fd_socket_norace.cc new file mode 100644 index 000000000000..243fc9de2238 --- /dev/null +++ b/lib/tsan/lit_tests/fd_socket_norace.cc @@ -0,0 +1,52 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +struct sockaddr_in addr; +int X; + +void *ClientThread(void *x) { + X = 42; + int c = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (connect(c, (struct sockaddr*)&addr, sizeof(addr))) { + perror("connect"); + exit(1); + } + if (send(c, "a", 1, 0) != 1) { + perror("send"); + exit(1); + } + close(c); + return NULL; +} + +int main() { + int s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + addr.sin_family = AF_INET; + inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr); + addr.sin_port = INADDR_ANY; + socklen_t len = sizeof(addr); + bind(s, (sockaddr*)&addr, len); + getsockname(s, (sockaddr*)&addr, &len); + listen(s, 10); + pthread_t t; + pthread_create(&t, 0, ClientThread, 0); + int c = accept(s, 0, 0); + char buf; + while (read(c, &buf, 1) != 1) { + } + X = 43; + close(c); + close(s); + pthread_join(t, 0); + printf("OK\n"); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race + diff --git a/lib/tsan/lit_tests/fd_socketpair_norace.cc b/lib/tsan/lit_tests/fd_socketpair_norace.cc new file mode 100644 index 000000000000..f91e4eca0fe9 --- /dev/null +++ b/lib/tsan/lit_tests/fd_socketpair_norace.cc @@ -0,0 +1,37 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> + +int fds[2]; +int X; + +void *Thread1(void *x) { + X = 42; + write(fds[1], "a", 1); + close(fds[1]); + return NULL; +} + +void *Thread2(void *x) { + char buf; + while (read(fds[0], &buf, 1) != 1) { + } + X = 43; + close(fds[0]); + return NULL; +} + +int main() { + socketpair(AF_UNIX, SOCK_STREAM, 0, fds); + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + printf("OK\n"); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race diff --git a/lib/tsan/lit_tests/fd_stdout_race.cc b/lib/tsan/lit_tests/fd_stdout_race.cc new file mode 100644 index 000000000000..6581fc503a1b --- /dev/null +++ b/lib/tsan/lit_tests/fd_stdout_race.cc @@ -0,0 +1,41 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +int X; + +void *Thread1(void *x) { + sleep(1); + int f = open("/dev/random", O_RDONLY); + char buf; + read(f, &buf, 1); + close(f); + X = 42; + return NULL; +} + +void *Thread2(void *x) { + X = 43; + write(STDOUT_FILENO, "a", 1); + return NULL; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: Write of size 4 +// CHECK: #0 Thread1 +// CHECK: Previous write of size 4 +// CHECK: #0 Thread2 + + diff --git a/lib/tsan/output_tests/free_race.c b/lib/tsan/lit_tests/free_race.c index fb7fbac77b04..7a2ec0cdbed0 100644 --- a/lib/tsan/output_tests/free_race.c +++ b/lib/tsan/lit_tests/free_race.c @@ -1,3 +1,4 @@ +// RUN: %clang_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s #include <pthread.h> #include <stdlib.h> #include <stdio.h> @@ -15,7 +16,7 @@ void *Thread1(void *x) { } void *Thread2(void *x) { - usleep(1000000); + sleep(1); pthread_mutex_lock(&mtx); mem[0] = 42; pthread_mutex_unlock(&mtx); @@ -34,10 +35,9 @@ int main() { } // CHECK: WARNING: ThreadSanitizer: heap-use-after-free -// CHECK: Write of size 4 at {{.*}} by main thread: +// CHECK: Write of size 4 at {{.*}} by main thread{{.*}}: // CHECK: #0 Thread2 // CHECK: #1 main -// CHECK: Previous write of size 8 at {{.*}} by thread 1: +// CHECK: Previous write of size 8 at {{.*}} by thread T1{{.*}}: // CHECK: #0 free // CHECK: #1 Thread1 - diff --git a/lib/tsan/output_tests/free_race2.c b/lib/tsan/lit_tests/free_race2.c index 7b2bdec039e1..095f82ea0818 100644 --- a/lib/tsan/output_tests/free_race2.c +++ b/lib/tsan/lit_tests/free_race2.c @@ -1,3 +1,4 @@ +// RUN: %clang_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s #include <stdlib.h> void __attribute__((noinline)) foo(int *mem) { @@ -23,4 +24,3 @@ int main() { // CHECK: #0 free // CHECK: #1 foo // CHECK: #2 main - diff --git a/lib/tsan/lit_tests/global_race.cc b/lib/tsan/lit_tests/global_race.cc new file mode 100644 index 000000000000..0892d07da2cb --- /dev/null +++ b/lib/tsan/lit_tests/global_race.cc @@ -0,0 +1,25 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <stddef.h> + +int GlobalData[10]; + +void *Thread(void *a) { + GlobalData[2] = 42; + return 0; +} + +int main() { + fprintf(stderr, "addr=%p\n", GlobalData); + pthread_t t; + pthread_create(&t, 0, Thread, 0); + GlobalData[2] = 43; + pthread_join(t, 0); +} + +// CHECK: addr=[[ADDR:0x[0-9,a-f]+]] +// CHECK: WARNING: ThreadSanitizer: data race +// Requires llvm-symbolizer, so disabled for now. +// CHECK0: Location is global 'GlobalData' of size 40 at [[ADDR]] +// CHECK0: (global_race.cc.exe+0x[0-9,a-f]+) diff --git a/lib/tsan/output_tests/heap_race.cc b/lib/tsan/lit_tests/heap_race.cc index e92bb379737e..297f8dbdec7d 100644 --- a/lib/tsan/output_tests/heap_race.cc +++ b/lib/tsan/lit_tests/heap_race.cc @@ -1,3 +1,4 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s #include <pthread.h> #include <stdio.h> #include <stddef.h> diff --git a/lib/tsan/lit_tests/ignore_race.cc b/lib/tsan/lit_tests/ignore_race.cc new file mode 100644 index 000000000000..23d74d0ed840 --- /dev/null +++ b/lib/tsan/lit_tests/ignore_race.cc @@ -0,0 +1,31 @@ +// RUN: %clang_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +int Global; + +extern "C" void AnnotateIgnoreWritesBegin(const char *f, int l); +extern "C" void AnnotateIgnoreWritesEnd(const char *f, int l); +extern "C" void AnnotateIgnoreReadsBegin(const char *f, int l); +extern "C" void AnnotateIgnoreReadsEnd(const char *f, int l); + +void *Thread(void *x) { + AnnotateIgnoreWritesBegin(__FILE__, __LINE__); + AnnotateIgnoreReadsBegin(__FILE__, __LINE__); + Global = 42; + AnnotateIgnoreReadsEnd(__FILE__, __LINE__); + AnnotateIgnoreWritesEnd(__FILE__, __LINE__); + return 0; +} + +int main() { + pthread_t t; + pthread_create(&t, 0, Thread, 0); + sleep(1); + Global = 43; + pthread_join(t, 0); + printf("OK\n"); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race diff --git a/lib/tsan/lit_tests/java.h b/lib/tsan/lit_tests/java.h new file mode 100644 index 000000000000..7d61f5802864 --- /dev/null +++ b/lib/tsan/lit_tests/java.h @@ -0,0 +1,17 @@ +#include <pthread.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> + +extern "C" { +typedef unsigned long jptr; // NOLINT +void __tsan_java_init(jptr heap_begin, jptr heap_size); +int __tsan_java_fini(); +void __tsan_java_alloc(jptr ptr, jptr size); +void __tsan_java_free(jptr ptr, jptr size); +void __tsan_java_move(jptr src, jptr dst, jptr size); +void __tsan_java_mutex_lock(jptr addr); +void __tsan_java_mutex_unlock(jptr addr); +void __tsan_java_mutex_read_lock(jptr addr); +void __tsan_java_mutex_read_unlock(jptr addr); +} diff --git a/lib/tsan/lit_tests/java_alloc.cc b/lib/tsan/lit_tests/java_alloc.cc new file mode 100644 index 000000000000..4dbce70c31eb --- /dev/null +++ b/lib/tsan/lit_tests/java_alloc.cc @@ -0,0 +1,32 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include "java.h" + +int const kHeapSize = 1024 * 1024; + +void stress(jptr addr) { + for (jptr sz = 8; sz <= 32; sz <<= 1) { + for (jptr i = 0; i < kHeapSize / 4 / sz; i++) { + __tsan_java_alloc(addr + i * sz, sz); + } + __tsan_java_move(addr, addr + kHeapSize / 2, kHeapSize / 4); + __tsan_java_free(addr + kHeapSize / 2, kHeapSize / 4); + } +} + +void *Thread(void *p) { + stress((jptr)p); + return 0; +} + +int main() { + jptr jheap = (jptr)malloc(kHeapSize); + __tsan_java_init(jheap, kHeapSize); + pthread_t th; + pthread_create(&th, 0, Thread, (void*)(jheap + kHeapSize / 4)); + stress(jheap); + pthread_join(th, 0); + printf("OK\n"); + return __tsan_java_fini(); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race diff --git a/lib/tsan/lit_tests/java_lock.cc b/lib/tsan/lit_tests/java_lock.cc new file mode 100644 index 000000000000..f66f1e7097fa --- /dev/null +++ b/lib/tsan/lit_tests/java_lock.cc @@ -0,0 +1,33 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include "java.h" + +jptr varaddr; +jptr lockaddr; + +void *Thread(void *p) { + __tsan_java_mutex_lock(lockaddr); + *(int*)varaddr = 42; + __tsan_java_mutex_unlock(lockaddr); + return 0; +} + +int main() { + int const kHeapSize = 1024 * 1024; + void *jheap = malloc(kHeapSize); + __tsan_java_init((jptr)jheap, kHeapSize); + const int kBlockSize = 16; + __tsan_java_alloc((jptr)jheap, kBlockSize); + varaddr = (jptr)jheap; + lockaddr = (jptr)jheap + 8; + pthread_t th; + pthread_create(&th, 0, Thread, 0); + __tsan_java_mutex_lock(lockaddr); + *(int*)varaddr = 43; + __tsan_java_mutex_unlock(lockaddr); + pthread_join(th, 0); + __tsan_java_free((jptr)jheap, kBlockSize); + printf("OK\n"); + return __tsan_java_fini(); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race diff --git a/lib/tsan/lit_tests/java_lock_move.cc b/lib/tsan/lit_tests/java_lock_move.cc new file mode 100644 index 000000000000..48b5a5a88d33 --- /dev/null +++ b/lib/tsan/lit_tests/java_lock_move.cc @@ -0,0 +1,40 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include "java.h" + +jptr varaddr; +jptr lockaddr; +jptr varaddr2; +jptr lockaddr2; + +void *Thread(void *p) { + sleep(1); + __tsan_java_mutex_lock(lockaddr2); + *(int*)varaddr2 = 42; + __tsan_java_mutex_unlock(lockaddr2); + return 0; +} + +int main() { + int const kHeapSize = 1024 * 1024; + void *jheap = malloc(kHeapSize); + __tsan_java_init((jptr)jheap, kHeapSize); + const int kBlockSize = 64; + int const kMove = 1024; + __tsan_java_alloc((jptr)jheap, kBlockSize); + varaddr = (jptr)jheap; + lockaddr = (jptr)jheap + 46; + varaddr2 = varaddr + kMove; + lockaddr2 = lockaddr + kMove; + pthread_t th; + pthread_create(&th, 0, Thread, 0); + __tsan_java_mutex_lock(lockaddr); + *(int*)varaddr = 43; + __tsan_java_mutex_unlock(lockaddr); + __tsan_java_move(varaddr, varaddr2, kBlockSize); + pthread_join(th, 0); + __tsan_java_free(varaddr2, kBlockSize); + printf("OK\n"); + return __tsan_java_fini(); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race diff --git a/lib/tsan/lit_tests/java_race.cc b/lib/tsan/lit_tests/java_race.cc new file mode 100644 index 000000000000..722bb6e8d09c --- /dev/null +++ b/lib/tsan/lit_tests/java_race.cc @@ -0,0 +1,23 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include "java.h" + +void *Thread(void *p) { + *(int*)p = 42; + return 0; +} + +int main() { + int const kHeapSize = 1024 * 1024; + void *jheap = malloc(kHeapSize); + __tsan_java_init((jptr)jheap, kHeapSize); + const int kBlockSize = 16; + __tsan_java_alloc((jptr)jheap, kBlockSize); + pthread_t th; + pthread_create(&th, 0, Thread, jheap); + *(int*)jheap = 43; + pthread_join(th, 0); + __tsan_java_free((jptr)jheap, kBlockSize); + return __tsan_java_fini(); +} + +// CHECK: WARNING: ThreadSanitizer: data race diff --git a/lib/tsan/lit_tests/java_race_move.cc b/lib/tsan/lit_tests/java_race_move.cc new file mode 100644 index 000000000000..bb63ea985c58 --- /dev/null +++ b/lib/tsan/lit_tests/java_race_move.cc @@ -0,0 +1,31 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include "java.h" + +jptr varaddr; +jptr varaddr2; + +void *Thread(void *p) { + sleep(1); + *(int*)varaddr2 = 42; + return 0; +} + +int main() { + int const kHeapSize = 1024 * 1024; + void *jheap = malloc(kHeapSize); + __tsan_java_init((jptr)jheap, kHeapSize); + const int kBlockSize = 64; + int const kMove = 1024; + __tsan_java_alloc((jptr)jheap, kBlockSize); + varaddr = (jptr)jheap + 16; + varaddr2 = varaddr + kMove; + pthread_t th; + pthread_create(&th, 0, Thread, 0); + *(int*)varaddr = 43; + __tsan_java_move(varaddr, varaddr2, kBlockSize); + pthread_join(th, 0); + __tsan_java_free(varaddr2, kBlockSize); + return __tsan_java_fini(); +} + +// CHECK: WARNING: ThreadSanitizer: data race diff --git a/lib/tsan/lit_tests/java_rwlock.cc b/lib/tsan/lit_tests/java_rwlock.cc new file mode 100644 index 000000000000..1e8940afd7d0 --- /dev/null +++ b/lib/tsan/lit_tests/java_rwlock.cc @@ -0,0 +1,33 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include "java.h" + +jptr varaddr; +jptr lockaddr; + +void *Thread(void *p) { + __tsan_java_mutex_read_lock(lockaddr); + *(int*)varaddr = 42; + __tsan_java_mutex_read_unlock(lockaddr); + return 0; +} + +int main() { + int const kHeapSize = 1024 * 1024; + void *jheap = malloc(kHeapSize); + __tsan_java_init((jptr)jheap, kHeapSize); + const int kBlockSize = 16; + __tsan_java_alloc((jptr)jheap, kBlockSize); + varaddr = (jptr)jheap; + lockaddr = (jptr)jheap + 8; + pthread_t th; + pthread_create(&th, 0, Thread, 0); + __tsan_java_mutex_lock(lockaddr); + *(int*)varaddr = 43; + __tsan_java_mutex_unlock(lockaddr); + pthread_join(th, 0); + __tsan_java_free((jptr)jheap, kBlockSize); + printf("OK\n"); + return __tsan_java_fini(); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race diff --git a/lib/tsan/lit_tests/lit.cfg b/lib/tsan/lit_tests/lit.cfg new file mode 100644 index 000000000000..7e2db7b8fd0b --- /dev/null +++ b/lib/tsan/lit_tests/lit.cfg @@ -0,0 +1,93 @@ +# -*- Python -*- + +import os + +# Setup config name. +config.name = 'ThreadSanitizer' + +# Setup source root. +config.test_source_root = os.path.dirname(__file__) + +def DisplayNoConfigMessage(): + lit.fatal("No site specific configuration available! " + + "Try running your test from the build tree or running " + + "make check-tsan") + +# Figure out LLVM source root. +llvm_src_root = getattr(config, 'llvm_src_root', None) +if llvm_src_root is None: + # We probably haven't loaded the site-specific configuration: the user + # is likely trying to run a test file directly, and the site configuration + # wasn't created by the build system. + tsan_site_cfg = lit.params.get('tsan_site_config', None) + if (tsan_site_cfg) and (os.path.exists(tsan_site_cfg)): + lit.load_config(config, tsan_site_cfg) + raise SystemExit + + # Try to guess the location of site-specific configuration using llvm-config + # util that can point where the build tree is. + llvm_config = lit.util.which("llvm-config", config.environment["PATH"]) + if not llvm_config: + DisplayNoConfigMessage() + + # Validate that llvm-config points to the same source tree. + llvm_src_root = lit.util.capture(["llvm-config", "--src-root"]).strip() + tsan_test_src_root = os.path.join(llvm_src_root, "projects", "compiler-rt", + "lib", "tsan", "lit_tests") + if (os.path.realpath(tsan_test_src_root) != + os.path.realpath(config.test_source_root)): + DisplayNoConfigMessage() + + # Find out the presumed location of generated site config. + llvm_obj_root = lit.util.capture(["llvm-config", "--obj-root"]).strip() + tsan_site_cfg = os.path.join(llvm_obj_root, "projects", "compiler-rt", + "lib", "tsan", "lit_tests", "lit.site.cfg") + if (not tsan_site_cfg) or (not os.path.exists(tsan_site_cfg)): + DisplayNoConfigMessage() + + lit.load_config(config, tsan_site_cfg) + raise SystemExit + +# Setup attributes common for all compiler-rt projects. +compiler_rt_lit_cfg = os.path.join(llvm_src_root, "projects", "compiler-rt", + "lib", "lit.common.cfg") +if (not compiler_rt_lit_cfg) or (not os.path.exists(compiler_rt_lit_cfg)): + lit.fatal("Can't find common compiler-rt lit config at: %r" + % compiler_rt_lit_cfg) +lit.load_config(config, compiler_rt_lit_cfg) + +# Setup environment variables for running ThreadSanitizer. +tsan_options = "atexit_sleep_ms=0" +# Get path to external LLVM symbolizer to run ThreadSanitizer output tests. +llvm_tools_dir = getattr(config, 'llvm_tools_dir', None) +if llvm_tools_dir: + llvm_symbolizer_path = os.path.join(llvm_tools_dir, "llvm-symbolizer") + tsan_options += " " + "external_symbolizer_path=" + llvm_symbolizer_path + +config.environment['TSAN_OPTIONS'] = tsan_options + +# Setup default compiler flags used with -fsanitize=thread option. +# FIXME: Review the set of required flags and check if it can be reduced. +clang_tsan_cflags = ("-fsanitize=thread " + + "-fPIE " + + "-fno-builtin " + + "-g " + + "-Wall " + + "-pie " + + "-lpthread " + + "-ldl ") +clang_tsan_cxxflags = "-ccc-cxx " + clang_tsan_cflags +config.substitutions.append( ("%clangxx_tsan ", (" " + config.clang + " " + + clang_tsan_cxxflags + " ")) ) +config.substitutions.append( ("%clang_tsan ", (" " + config.clang + " " + + clang_tsan_cflags + " ")) ) + +# Define CHECK-%os to check for OS-dependent output. +config.substitutions.append( ('CHECK-%os', ("CHECK-" + config.host_os))) + +# Default test suffixes. +config.suffixes = ['.c', '.cc', '.cpp'] + +# ThreadSanitizer tests are currently supported on Linux only. +if config.host_os not in ['Linux']: + config.unsupported = True diff --git a/lib/tsan/lit_tests/lit.site.cfg.in b/lib/tsan/lit_tests/lit.site.cfg.in new file mode 100644 index 000000000000..b1c6ccf544ea --- /dev/null +++ b/lib/tsan/lit_tests/lit.site.cfg.in @@ -0,0 +1,19 @@ +## Autogenerated by LLVM/Clang configuration. +# Do not edit! + +config.clang = "@LLVM_BINARY_DIR@/bin/clang" +config.host_os = "@HOST_OS@" +config.llvm_src_root = "@LLVM_SOURCE_DIR@" +config.llvm_tools_dir = "@LLVM_TOOLS_DIR@" +config.target_triple = "@TARGET_TRIPLE@" + +# LLVM tools dir can be passed in lit parameters, so try to +# apply substitution. +try: + config.llvm_tools_dir = config.llvm_tools_dir % lit.params +except KeyError,e: + key, = e.args + lit.fatal("unable to find %r parameter, use '--param=%s=VALUE'" % (key, key)) + +# Let the main config do the real work. +lit.load_config(config, "@CMAKE_CURRENT_SOURCE_DIR@/lit.cfg") diff --git a/lib/tsan/output_tests/memcpy_race.cc b/lib/tsan/lit_tests/memcpy_race.cc index c6b79a709e48..806740dda241 100644 --- a/lib/tsan/output_tests/memcpy_race.cc +++ b/lib/tsan/lit_tests/memcpy_race.cc @@ -1,3 +1,4 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s #include <pthread.h> #include <stddef.h> #include <stdio.h> @@ -14,7 +15,7 @@ void *Thread1(void *x) { } void *Thread2(void *x) { - usleep(500*1000); + sleep(1); memcpy(data+3, data2, 4); return NULL; } @@ -31,10 +32,9 @@ int main() { // CHECK: addr=[[ADDR:0x[0-9,a-f]+]] // CHECK: WARNING: ThreadSanitizer: data race -// CHECK: Write of size 1 at [[ADDR]] by thread 2: +// CHECK: Write of size 1 at [[ADDR]] by thread T2: // CHECK: #0 memcpy // CHECK: #1 Thread2 -// CHECK: Previous write of size 1 at [[ADDR]] by thread 1: +// CHECK: Previous write of size 1 at [[ADDR]] by thread T1: // CHECK: #0 memcpy // CHECK: #1 Thread1 - diff --git a/lib/tsan/output_tests/mop_with_offset.cc b/lib/tsan/lit_tests/mop_with_offset.cc index fc497bfb4a74..0c11ef6b9187 100644 --- a/lib/tsan/output_tests/mop_with_offset.cc +++ b/lib/tsan/lit_tests/mop_with_offset.cc @@ -1,3 +1,4 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s #include <pthread.h> #include <stddef.h> #include <stdio.h> @@ -10,7 +11,7 @@ void *Thread1(void *x) { } void *Thread2(void *x) { - usleep(500*1000); + sleep(1); char *p = (char*)x; p[2] = 1; return NULL; @@ -31,6 +32,5 @@ int main() { // CHECK: ptr1=[[PTR1:0x[0-9,a-f]+]] // CHECK: ptr2=[[PTR2:0x[0-9,a-f]+]] // CHECK: WARNING: ThreadSanitizer: data race -// CHECK: Write of size 1 at [[PTR2]] by thread 2: -// CHECK: Previous write of size 4 at [[PTR1]] by thread 1: - +// CHECK: Write of size 1 at [[PTR2]] by thread T2: +// CHECK: Previous write of size 4 at [[PTR1]] by thread T1: diff --git a/lib/tsan/output_tests/mop_with_offset2.cc b/lib/tsan/lit_tests/mop_with_offset2.cc index bbeda554929f..ee0d64a0afbf 100644 --- a/lib/tsan/output_tests/mop_with_offset2.cc +++ b/lib/tsan/lit_tests/mop_with_offset2.cc @@ -1,10 +1,11 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s #include <pthread.h> #include <stddef.h> #include <stdio.h> #include <unistd.h> void *Thread1(void *x) { - usleep(500*1000); + sleep(1); int *p = (int*)x; p[0] = 1; return NULL; @@ -31,6 +32,5 @@ int main() { // CHECK: ptr1=[[PTR1:0x[0-9,a-f]+]] // CHECK: ptr2=[[PTR2:0x[0-9,a-f]+]] // CHECK: WARNING: ThreadSanitizer: data race -// CHECK: Write of size 4 at [[PTR1]] by thread 1: -// CHECK: Previous write of size 1 at [[PTR2]] by thread 2: - +// CHECK: Write of size 4 at [[PTR1]] by thread T1: +// CHECK: Previous write of size 1 at [[PTR2]] by thread T2: diff --git a/lib/tsan/lit_tests/mutex_destroy_locked.cc b/lib/tsan/lit_tests/mutex_destroy_locked.cc new file mode 100644 index 000000000000..991eaf5426e2 --- /dev/null +++ b/lib/tsan/lit_tests/mutex_destroy_locked.cc @@ -0,0 +1,21 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <unistd.h> + +int main() { + pthread_mutex_t m; + pthread_mutex_init(&m, 0); + pthread_mutex_lock(&m); + pthread_mutex_destroy(&m); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: destroy of a locked mutex +// CHECK: #0 pthread_mutex_destroy +// CHECK: #1 main +// CHECK: and: +// CHECK: #0 pthread_mutex_lock +// CHECK: #1 main +// CHECK: Mutex {{.*}} created at: +// CHECK: #0 pthread_mutex_init +// CHECK: #1 main diff --git a/lib/tsan/lit_tests/mutexset1.cc b/lib/tsan/lit_tests/mutexset1.cc new file mode 100644 index 000000000000..f32a770ab075 --- /dev/null +++ b/lib/tsan/lit_tests/mutexset1.cc @@ -0,0 +1,37 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +int Global; +pthread_mutex_t mtx; + +void *Thread1(void *x) { + sleep(1); + pthread_mutex_lock(&mtx); + Global++; + pthread_mutex_unlock(&mtx); + return NULL; +} + +void *Thread2(void *x) { + Global--; + return NULL; +} + +int main() { + // CHECK: WARNING: ThreadSanitizer: data race + // CHECK: Write of size 4 at {{.*}} by thread T1 + // CHECK: (mutexes: write [[M1:M[0-9]+]]): + // CHECK: Previous write of size 4 at {{.*}} by thread T2: + // CHECK: Mutex [[M1]] created at: + // CHECK: #0 pthread_mutex_init + // CHECK: #1 main {{.*}}/mutexset1.cc:[[@LINE+1]] + pthread_mutex_init(&mtx, 0); + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + pthread_mutex_destroy(&mtx); +} diff --git a/lib/tsan/lit_tests/mutexset2.cc b/lib/tsan/lit_tests/mutexset2.cc new file mode 100644 index 000000000000..15d230332512 --- /dev/null +++ b/lib/tsan/lit_tests/mutexset2.cc @@ -0,0 +1,37 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +int Global; +pthread_mutex_t mtx; + +void *Thread1(void *x) { + pthread_mutex_lock(&mtx); + Global++; + pthread_mutex_unlock(&mtx); + return NULL; +} + +void *Thread2(void *x) { + sleep(1); + Global--; + return NULL; +} + +int main() { + // CHECK: WARNING: ThreadSanitizer: data race + // CHECK: Write of size 4 at {{.*}} by thread T2: + // CHECK: Previous write of size 4 at {{.*}} by thread T1 + // CHECK: (mutexes: write [[M1:M[0-9]+]]): + // CHECK: Mutex [[M1]] created at: + // CHECK: #0 pthread_mutex_init + // CHECK: #1 main {{.*}}/mutexset2.cc:[[@LINE+1]] + pthread_mutex_init(&mtx, 0); + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + pthread_mutex_destroy(&mtx); +} diff --git a/lib/tsan/lit_tests/mutexset3.cc b/lib/tsan/lit_tests/mutexset3.cc new file mode 100644 index 000000000000..6ac7ad15e4f9 --- /dev/null +++ b/lib/tsan/lit_tests/mutexset3.cc @@ -0,0 +1,45 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +int Global; +pthread_mutex_t mtx1; +pthread_mutex_t mtx2; + +void *Thread1(void *x) { + sleep(1); + pthread_mutex_lock(&mtx1); + pthread_mutex_lock(&mtx2); + Global++; + pthread_mutex_unlock(&mtx2); + pthread_mutex_unlock(&mtx1); + return NULL; +} + +void *Thread2(void *x) { + Global--; + return NULL; +} + +int main() { + // CHECK: WARNING: ThreadSanitizer: data race + // CHECK: Write of size 4 at {{.*}} by thread T1 + // CHECK: (mutexes: write [[M1:M[0-9]+]], write [[M2:M[0-9]+]]): + // CHECK: Previous write of size 4 at {{.*}} by thread T2: + // CHECK: Mutex [[M1]] created at: + // CHECK: #0 pthread_mutex_init + // CHECK: #1 main {{.*}}/mutexset3.cc:[[@LINE+4]] + // CHECK: Mutex [[M2]] created at: + // CHECK: #0 pthread_mutex_init + // CHECK: #1 main {{.*}}/mutexset3.cc:[[@LINE+2]] + pthread_mutex_init(&mtx1, 0); + pthread_mutex_init(&mtx2, 0); + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + pthread_mutex_destroy(&mtx1); + pthread_mutex_destroy(&mtx2); +} diff --git a/lib/tsan/lit_tests/mutexset4.cc b/lib/tsan/lit_tests/mutexset4.cc new file mode 100644 index 000000000000..75684cf9ae5b --- /dev/null +++ b/lib/tsan/lit_tests/mutexset4.cc @@ -0,0 +1,45 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +int Global; +pthread_mutex_t mtx1; +pthread_mutex_t mtx2; + +void *Thread1(void *x) { + pthread_mutex_lock(&mtx1); + pthread_mutex_lock(&mtx2); + Global++; + pthread_mutex_unlock(&mtx2); + pthread_mutex_unlock(&mtx1); + return NULL; +} + +void *Thread2(void *x) { + sleep(1); + Global--; + return NULL; +} + +int main() { + // CHECK: WARNING: ThreadSanitizer: data race + // CHECK: Write of size 4 at {{.*}} by thread T2: + // CHECK: Previous write of size 4 at {{.*}} by thread T1 + // CHECK: (mutexes: write [[M1:M[0-9]+]], write [[M2:M[0-9]+]]): + // CHECK: Mutex [[M1]] created at: + // CHECK: #0 pthread_mutex_init + // CHECK: #1 main {{.*}}/mutexset4.cc:[[@LINE+4]] + // CHECK: Mutex [[M2]] created at: + // CHECK: #0 pthread_mutex_init + // CHECK: #1 main {{.*}}/mutexset4.cc:[[@LINE+2]] + pthread_mutex_init(&mtx1, 0); + pthread_mutex_init(&mtx2, 0); + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + pthread_mutex_destroy(&mtx1); + pthread_mutex_destroy(&mtx2); +} diff --git a/lib/tsan/lit_tests/mutexset5.cc b/lib/tsan/lit_tests/mutexset5.cc new file mode 100644 index 000000000000..6e75810aff22 --- /dev/null +++ b/lib/tsan/lit_tests/mutexset5.cc @@ -0,0 +1,46 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +int Global; +pthread_mutex_t mtx1; +pthread_mutex_t mtx2; + +void *Thread1(void *x) { + sleep(1); + pthread_mutex_lock(&mtx1); + Global++; + pthread_mutex_unlock(&mtx1); + return NULL; +} + +void *Thread2(void *x) { + pthread_mutex_lock(&mtx2); + Global--; + pthread_mutex_unlock(&mtx2); + return NULL; +} + +int main() { + // CHECK: WARNING: ThreadSanitizer: data race + // CHECK: Write of size 4 at {{.*}} by thread T1 + // CHECK: (mutexes: write [[M1:M[0-9]+]]): + // CHECK: Previous write of size 4 at {{.*}} by thread T2 + // CHECK: (mutexes: write [[M2:M[0-9]+]]): + // CHECK: Mutex [[M1]] created at: + // CHECK: #0 pthread_mutex_init + // CHECK: #1 main {{.*}}/mutexset5.cc:[[@LINE+4]] + // CHECK: Mutex [[M2]] created at: + // CHECK: #0 pthread_mutex_init + // CHECK: #1 main {{.*}}/mutexset5.cc:[[@LINE+5]] + pthread_mutex_init(&mtx1, 0); + pthread_mutex_init(&mtx2, 0); + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + pthread_mutex_destroy(&mtx1); + pthread_mutex_destroy(&mtx2); +} diff --git a/lib/tsan/lit_tests/mutexset6.cc b/lib/tsan/lit_tests/mutexset6.cc new file mode 100644 index 000000000000..4b19a12e0434 --- /dev/null +++ b/lib/tsan/lit_tests/mutexset6.cc @@ -0,0 +1,53 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +int Global; +pthread_mutex_t mtx1; +pthread_spinlock_t mtx2; +pthread_rwlock_t mtx3; + +void *Thread1(void *x) { + sleep(1); + pthread_mutex_lock(&mtx1); + Global++; + pthread_mutex_unlock(&mtx1); + return NULL; +} + +void *Thread2(void *x) { + pthread_mutex_lock(&mtx1); + pthread_mutex_unlock(&mtx1); + pthread_spin_lock(&mtx2); + pthread_rwlock_rdlock(&mtx3); + Global--; + pthread_spin_unlock(&mtx2); + pthread_rwlock_unlock(&mtx3); + return NULL; +} + +int main() { + // CHECK: WARNING: ThreadSanitizer: data race + // CHECK: Write of size 4 at {{.*}} by thread T1 + // CHECK: (mutexes: write [[M1:M[0-9]+]]): + // CHECK: Previous write of size 4 at {{.*}} by thread T2 + // CHECK: (mutexes: write [[M2:M[0-9]+]], read [[M3:M[0-9]+]]): + // CHECK: Mutex [[M1]] created at: + // CHECK: #1 main {{.*}}/mutexset6.cc:[[@LINE+5]] + // CHECK: Mutex [[M2]] created at: + // CHECK: #1 main {{.*}}/mutexset6.cc:[[@LINE+4]] + // CHECK: Mutex [[M3]] created at: + // CHECK: #1 main {{.*}}/mutexset6.cc:[[@LINE+3]] + pthread_mutex_init(&mtx1, 0); + pthread_spin_init(&mtx2, 0); + pthread_rwlock_init(&mtx3, 0); + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + pthread_mutex_destroy(&mtx1); + pthread_spin_destroy(&mtx2); + pthread_rwlock_destroy(&mtx3); +} diff --git a/lib/tsan/lit_tests/mutexset7.cc b/lib/tsan/lit_tests/mutexset7.cc new file mode 100644 index 000000000000..141bde2b5015 --- /dev/null +++ b/lib/tsan/lit_tests/mutexset7.cc @@ -0,0 +1,38 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +int Global; + +void *Thread1(void *x) { + sleep(1); + Global++; + return NULL; +} + +void *Thread2(void *x) { + pthread_mutex_t mtx; + pthread_mutex_init(&mtx, 0); + pthread_mutex_lock(&mtx); + Global--; + pthread_mutex_unlock(&mtx); + pthread_mutex_destroy(&mtx); + return NULL; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: Write of size 4 at {{.*}} by thread T1: +// CHECK: Previous write of size 4 at {{.*}} by thread T2 +// CHECK: (mutexes: write [[M1:M[0-9]+]]): +// CHECK: Mutex [[M1]] is already destroyed +// CHECK-NOT: Mutex {{.*}} created at + diff --git a/lib/tsan/output_tests/race_on_barrier.c b/lib/tsan/lit_tests/race_on_barrier.c index 98d7a1d847f9..3e76f8bf5e20 100644 --- a/lib/tsan/output_tests/race_on_barrier.c +++ b/lib/tsan/lit_tests/race_on_barrier.c @@ -1,3 +1,4 @@ +// RUN: %clang_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s #include <pthread.h> #include <stdio.h> #include <stddef.h> @@ -13,7 +14,7 @@ void *Thread1(void *x) { } void *Thread2(void *x) { - usleep(1000000); + sleep(1); pthread_barrier_wait(&B); return NULL; } @@ -28,4 +29,3 @@ int main() { } // CHECK: WARNING: ThreadSanitizer: data race - diff --git a/lib/tsan/output_tests/race_on_barrier2.c b/lib/tsan/lit_tests/race_on_barrier2.c index dbdb6b557004..46a4f50b133d 100644 --- a/lib/tsan/output_tests/race_on_barrier2.c +++ b/lib/tsan/lit_tests/race_on_barrier2.c @@ -1,3 +1,4 @@ +// RUN: %clang_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s #include <pthread.h> #include <stdio.h> #include <stddef.h> diff --git a/lib/tsan/lit_tests/race_on_heap.cc b/lib/tsan/lit_tests/race_on_heap.cc new file mode 100644 index 000000000000..dc679e8bf3f9 --- /dev/null +++ b/lib/tsan/lit_tests/race_on_heap.cc @@ -0,0 +1,47 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdlib.h> +#include <stdio.h> + +void *Thread1(void *p) { + *(int*)p = 42; + return 0; +} + +void *Thread2(void *p) { + *(int*)p = 44; + return 0; +} + +void *alloc() { + return malloc(99); +} + +void *AllocThread(void* arg) { + return alloc(); +} + +int main() { + void *p = 0; + pthread_t t[2]; + pthread_create(&t[0], 0, AllocThread, 0); + pthread_join(t[0], &p); + fprintf(stderr, "addr=%p\n", p); + pthread_create(&t[0], 0, Thread1, (char*)p + 16); + pthread_create(&t[1], 0, Thread2, (char*)p + 16); + pthread_join(t[0], 0); + pthread_join(t[1], 0); + return 0; +} + +// CHECK: addr=[[ADDR:0x[0-9,a-f]+]] +// CHECK: WARNING: ThreadSanitizer: data race +// ... +// CHECK: Location is heap block of size 99 at [[ADDR]] allocated by thread T1: +// CHCEKL #0 malloc +// CHECK: #1 alloc +// CHECK: #2 AllocThread +// ... +// CHECK: Thread T1 (tid={{.*}}, finished) created by main thread at: +// CHECK: #0 pthread_create +// CHECK: #1 main diff --git a/lib/tsan/output_tests/race_on_mutex.c b/lib/tsan/lit_tests/race_on_mutex.c index 45c75be782d1..de1c2d4160a6 100644 --- a/lib/tsan/output_tests/race_on_mutex.c +++ b/lib/tsan/lit_tests/race_on_mutex.c @@ -1,3 +1,4 @@ +// RUN: %clang_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s #include <pthread.h> #include <stdio.h> #include <stddef.h> @@ -15,7 +16,7 @@ void *Thread1(void *x) { } void *Thread2(void *x) { - usleep(1000000); + sleep(1); pthread_mutex_lock(&Mtx); Global = 43; pthread_mutex_unlock(&Mtx); @@ -33,9 +34,9 @@ int main() { } // CHECK: WARNING: ThreadSanitizer: data race -// CHECK-NEXT: Read of size 1 at {{.*}} by thread 2: -// CHECK-NEXT: #0 pthread_mutex_lock {{.*}} ({{.*}}) -// CHECK-NEXT: #1 Thread2 {{.*}}race_on_mutex.c:19{{(:3)?}} ({{.*}}) -// CHECK-NEXT: Previous write of size 1 at {{.*}} by thread 1: +// CHECK-NEXT: Read of size 1 at {{.*}} by thread T2: +// CHECK-NEXT: #0 pthread_mutex_lock +// CHECK-NEXT: #1 Thread2{{.*}} {{.*}}race_on_mutex.c:20{{(:3)?}} ({{.*}}) +// CHECK: Previous write of size 1 at {{.*}} by thread T1: // CHECK-NEXT: #0 pthread_mutex_init {{.*}} ({{.*}}) -// CHECK-NEXT: #1 Thread1 {{.*}}race_on_mutex.c:10{{(:3)?}} ({{.*}}) +// CHECK-NEXT: #1 Thread1{{.*}} {{.*}}race_on_mutex.c:11{{(:3)?}} ({{.*}}) diff --git a/lib/tsan/lit_tests/race_on_read.cc b/lib/tsan/lit_tests/race_on_read.cc new file mode 100644 index 000000000000..7d226814816e --- /dev/null +++ b/lib/tsan/lit_tests/race_on_read.cc @@ -0,0 +1,32 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +int fd; +char buf; + +void *Thread(void *x) { + read(fd, &buf, 1); + return NULL; +} + +int main() { + fd = open("/dev/random", O_RDONLY); + if (fd < 0) return 1; + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread, NULL); + pthread_create(&t[1], NULL, Thread, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + close(fd); +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: Write of size 1 +// CHECK: #0 read +// CHECK: Previous write of size 1 +// CHECK: #0 read diff --git a/lib/tsan/output_tests/race_with_finished_thread.cc b/lib/tsan/lit_tests/race_with_finished_thread.cc index 1f60f4ba349b..a267290e661e 100644 --- a/lib/tsan/output_tests/race_with_finished_thread.cc +++ b/lib/tsan/lit_tests/race_with_finished_thread.cc @@ -1,3 +1,4 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s #include <pthread.h> #include <stddef.h> #include <stdio.h> @@ -18,7 +19,7 @@ void *Thread1(void *x) { } void *Thread2(void *x) { - usleep(1000*1000); + sleep(1); g_data = 43; return NULL; } @@ -33,11 +34,10 @@ int main() { } // CHECK: WARNING: ThreadSanitizer: data race -// CHECK: Write of size 4 at {{.*}} by thread 2: -// CHECK: Previous write of size 4 at {{.*}} by thread 1: +// CHECK: Write of size 4 at {{.*}} by thread T2: +// CHECK: Previous write of size 4 at {{.*}} by thread T1: // CHECK: #0 foobar // CHECK: #1 Thread1 -// CHECK: Thread 1 (finished) created at: +// CHECK: Thread T1 (tid={{.*}}, finished) created by main thread at: // CHECK: #0 pthread_create // CHECK: #1 main - diff --git a/lib/tsan/lit_tests/signal_errno.cc b/lib/tsan/lit_tests/signal_errno.cc new file mode 100644 index 000000000000..af9ccce9045a --- /dev/null +++ b/lib/tsan/lit_tests/signal_errno.cc @@ -0,0 +1,42 @@ +// RUN: %clang_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> +#include <sys/types.h> +#include <unistd.h> +#include <errno.h> + +pthread_t mainth; +volatile int done; + +static void handler(int, siginfo_t *s, void *c) { + errno = 1; + done = 1; +} + +static void* sendsignal(void *p) { + pthread_kill(mainth, SIGPROF); + return 0; +} + +int main() { + mainth = pthread_self(); + struct sigaction act = {}; + act.sa_sigaction = &handler; + sigaction(SIGPROF, &act, 0); + pthread_t th; + pthread_create(&th, 0, sendsignal, 0); + while (done == 0) { + volatile char *p = (char*)malloc(1); + p[0] = 0; + free((void*)p); + pthread_yield(); + } + pthread_join(th, 0); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: signal handler spoils errno +// CHECK: #0 handler(int, siginfo*, void*) {{.*}}signal_errno.cc + diff --git a/lib/tsan/lit_tests/signal_malloc.cc b/lib/tsan/lit_tests/signal_malloc.cc new file mode 100644 index 000000000000..cee997cdb763 --- /dev/null +++ b/lib/tsan/lit_tests/signal_malloc.cc @@ -0,0 +1,25 @@ +// RUN: %clang_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> +#include <sys/types.h> +#include <unistd.h> + +static void handler(int, siginfo_t*, void*) { + // CHECK: WARNING: ThreadSanitizer: signal-unsafe call inside of a signal + // CHECK: #0 malloc + // CHECK: #1 handler(int, siginfo*, void*) {{.*}}signal_malloc.cc:[[@LINE+1]] + volatile char *p = (char*)malloc(1); + p[0] = 0; + free((void*)p); +} + +int main() { + struct sigaction act = {}; + act.sa_sigaction = &handler; + sigaction(SIGPROF, &act, 0); + kill(getpid(), SIGPROF); + sleep(1); + return 0; +} + diff --git a/lib/tsan/output_tests/simple_race.c b/lib/tsan/lit_tests/simple_race.c index ed831fd8c5a3..44aff897406a 100644 --- a/lib/tsan/output_tests/simple_race.c +++ b/lib/tsan/lit_tests/simple_race.c @@ -1,3 +1,4 @@ +// RUN: %clang_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s #include <pthread.h> #include <stdio.h> diff --git a/lib/tsan/output_tests/simple_race.cc b/lib/tsan/lit_tests/simple_race.cc index 8d2cabff772c..ec29c92ee1a8 100644 --- a/lib/tsan/output_tests/simple_race.cc +++ b/lib/tsan/lit_tests/simple_race.cc @@ -1,3 +1,4 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s #include <pthread.h> #include <stdio.h> diff --git a/lib/tsan/lit_tests/simple_stack.c b/lib/tsan/lit_tests/simple_stack.c new file mode 100644 index 000000000000..4539cb7c1f37 --- /dev/null +++ b/lib/tsan/lit_tests/simple_stack.c @@ -0,0 +1,66 @@ +// RUN: %clang_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +int Global; + +void __attribute__((noinline)) foo1() { + Global = 42; +} + +void __attribute__((noinline)) bar1() { + volatile int tmp = 42; (void)tmp; + foo1(); +} + +void __attribute__((noinline)) foo2() { + volatile int v = Global; (void)v; +} + +void __attribute__((noinline)) bar2() { + volatile int tmp = 42; (void)tmp; + foo2(); +} + +void *Thread1(void *x) { + sleep(1); + bar1(); + return NULL; +} + +void *Thread2(void *x) { + bar2(); + return NULL; +} + +void StartThread(pthread_t *t, void *(*f)(void*)) { + pthread_create(t, NULL, f, NULL); +} + +int main() { + pthread_t t[2]; + StartThread(&t[0], Thread1); + StartThread(&t[1], Thread2); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK-NEXT: Write of size 4 at {{.*}} by thread T1: +// CHECK-NEXT: #0 foo1{{.*}} {{.*}}simple_stack.c:9{{(:3)?}} ({{.*}}) +// CHECK-NEXT: #1 bar1{{.*}} {{.*}}simple_stack.c:14{{(:3)?}} ({{.*}}) +// CHECK-NEXT: #2 Thread1{{.*}} {{.*}}simple_stack.c:28{{(:3)?}} ({{.*}}) +// CHECK: Previous read of size 4 at {{.*}} by thread T2: +// CHECK-NEXT: #0 foo2{{.*}} {{.*}}simple_stack.c:18{{(:26)?}} ({{.*}}) +// CHECK-NEXT: #1 bar2{{.*}} {{.*}}simple_stack.c:23{{(:3)?}} ({{.*}}) +// CHECK-NEXT: #2 Thread2{{.*}} {{.*}}simple_stack.c:33{{(:3)?}} ({{.*}}) +// CHECK: Thread T1 (tid={{.*}}, running) created by main thread at: +// CHECK-NEXT: #0 pthread_create {{.*}} ({{.*}}) +// CHECK-NEXT: #1 StartThread{{.*}} {{.*}}simple_stack.c:38{{(:3)?}} ({{.*}}) +// CHECK-NEXT: #2 main{{.*}} {{.*}}simple_stack.c:43{{(:3)?}} ({{.*}}) +// CHECK: Thread T2 ({{.*}}) created by main thread at: +// CHECK-NEXT: #0 pthread_create {{.*}} ({{.*}}) +// CHECK-NEXT: #1 StartThread{{.*}} {{.*}}simple_stack.c:38{{(:3)?}} ({{.*}}) +// CHECK-NEXT: #2 main{{.*}} {{.*}}simple_stack.c:44{{(:3)?}} ({{.*}}) diff --git a/lib/tsan/lit_tests/simple_stack2.cc b/lib/tsan/lit_tests/simple_stack2.cc new file mode 100644 index 000000000000..bf27a15ffad5 --- /dev/null +++ b/lib/tsan/lit_tests/simple_stack2.cc @@ -0,0 +1,53 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +int Global; + +void __attribute__((noinline)) foo1() { + Global = 42; +} + +void __attribute__((noinline)) bar1() { + volatile int tmp = 42; + int tmp2 = tmp; + (void)tmp2; + foo1(); +} + +void __attribute__((noinline)) foo2() { + volatile int tmp = Global; + int tmp2 = tmp; + (void)tmp2; +} + +void __attribute__((noinline)) bar2() { + volatile int tmp = 42; + int tmp2 = tmp; + (void)tmp2; + foo2(); +} + +void *Thread1(void *x) { + sleep(1); + bar1(); + return NULL; +} + +int main() { + pthread_t t; + pthread_create(&t, NULL, Thread1, NULL); + bar2(); + pthread_join(t, NULL); +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK-NEXT: Write of size 4 at {{.*}} by thread T1: +// CHECK-NEXT: #0 foo1{{.*}} {{.*}}simple_stack2.cc:9{{(:3)?}} ({{.*}}) +// CHECK-NEXT: #1 bar1{{.*}} {{.*}}simple_stack2.cc:16{{(:3)?}} ({{.*}}) +// CHECK-NEXT: #2 Thread1{{.*}} {{.*}}simple_stack2.cc:34{{(:3)?}} ({{.*}}) +// CHECK: Previous read of size 4 at {{.*}} by main thread: +// CHECK-NEXT: #0 foo2{{.*}} {{.*}}simple_stack2.cc:20{{(:28)?}} ({{.*}}) +// CHECK-NEXT: #1 bar2{{.*}} {{.*}}simple_stack2.cc:29{{(:3)?}} ({{.*}}) +// CHECK-NEXT: #2 main{{.*}} {{.*}}simple_stack2.cc:41{{(:3)?}} ({{.*}}) diff --git a/lib/tsan/lit_tests/sleep_sync.cc b/lib/tsan/lit_tests/sleep_sync.cc new file mode 100644 index 000000000000..c3d47d311749 --- /dev/null +++ b/lib/tsan/lit_tests/sleep_sync.cc @@ -0,0 +1,30 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <unistd.h> + +int X = 0; + +void MySleep() { + sleep(1); +} + +void *Thread(void *p) { + MySleep(); // Assume the main thread has done the write. + X = 42; + return 0; +} + +int main() { + pthread_t t; + pthread_create(&t, 0, Thread, 0); + X = 43; + pthread_join(t, 0); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: data race +// ... +// CHECK: As if synchronized via sleep: +// CHECK-NEXT: #0 sleep +// CHECK-NEXT: #1 MySleep +// CHECK-NEXT: #2 Thread diff --git a/lib/tsan/lit_tests/sleep_sync2.cc b/lib/tsan/lit_tests/sleep_sync2.cc new file mode 100644 index 000000000000..d9961bccc808 --- /dev/null +++ b/lib/tsan/lit_tests/sleep_sync2.cc @@ -0,0 +1,22 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <unistd.h> + +int X = 0; + +void *Thread(void *p) { + X = 42; + return 0; +} + +int main() { + pthread_t t; + sleep(1); + pthread_create(&t, 0, Thread, 0); + X = 43; + pthread_join(t, 0); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK-NOT: As if synchronized via sleep diff --git a/lib/tsan/lit_tests/stack_race.cc b/lib/tsan/lit_tests/stack_race.cc new file mode 100644 index 000000000000..beeb57353bf3 --- /dev/null +++ b/lib/tsan/lit_tests/stack_race.cc @@ -0,0 +1,20 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stddef.h> + +void *Thread(void *a) { + *(int*)a = 43; + return 0; +} + +int main() { + int Var = 42; + pthread_t t; + pthread_create(&t, 0, Thread, &Var); + Var = 43; + pthread_join(t, 0); +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: Location is stack of main thread. + diff --git a/lib/tsan/lit_tests/stack_race2.cc b/lib/tsan/lit_tests/stack_race2.cc new file mode 100644 index 000000000000..5bdf1bd664a1 --- /dev/null +++ b/lib/tsan/lit_tests/stack_race2.cc @@ -0,0 +1,28 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stddef.h> +#include <unistd.h> + +void *Thread2(void *a) { + *(int*)a = 43; + return 0; +} + +void *Thread(void *a) { + int Var = 42; + pthread_t t; + pthread_create(&t, 0, Thread2, &Var); + Var = 42; + pthread_join(t, 0); + return 0; +} + +int main() { + pthread_t t; + pthread_create(&t, 0, Thread, 0); + pthread_join(t, 0); +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: Location is stack of thread T1. + diff --git a/lib/tsan/output_tests/static_init1.cc b/lib/tsan/lit_tests/static_init1.cc index 75d281954e1a..4faf5bc54743 100644 --- a/lib/tsan/output_tests/static_init1.cc +++ b/lib/tsan/lit_tests/static_init1.cc @@ -1,3 +1,4 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s #include <pthread.h> #include <stdlib.h> #include <stdio.h> @@ -20,6 +21,7 @@ int main() { pthread_create(&t[1], 0, Thread, 0); pthread_join(t[0], 0); pthread_join(t[1], 0); + printf("PASS\n"); } // CHECK-NOT: WARNING: ThreadSanitizer: data race diff --git a/lib/tsan/output_tests/static_init2.cc b/lib/tsan/lit_tests/static_init2.cc index f6e95965521a..96ef821a7525 100644 --- a/lib/tsan/output_tests/static_init2.cc +++ b/lib/tsan/lit_tests/static_init2.cc @@ -1,10 +1,11 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s #include <pthread.h> #include <stdlib.h> #include <stdio.h> struct Cache { int x; - Cache(int x) + explicit Cache(int x) : x(x) { } }; @@ -26,6 +27,7 @@ int main() { pthread_create(&t[1], 0, Thread, 0); pthread_join(t[0], 0); pthread_join(t[1], 0); + printf("PASS\n"); } // CHECK-NOT: WARNING: ThreadSanitizer: data race diff --git a/lib/tsan/output_tests/static_init3.cc b/lib/tsan/lit_tests/static_init3.cc index 718f811d0df4..40fd4b940f55 100644 --- a/lib/tsan/output_tests/static_init3.cc +++ b/lib/tsan/lit_tests/static_init3.cc @@ -1,3 +1,4 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s #include <pthread.h> #include <stdlib.h> #include <stdio.h> diff --git a/lib/tsan/output_tests/static_init4.cc b/lib/tsan/lit_tests/static_init4.cc index cdacbce80026..5ecc39926a23 100644 --- a/lib/tsan/output_tests/static_init4.cc +++ b/lib/tsan/lit_tests/static_init4.cc @@ -1,3 +1,4 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s #include <pthread.h> #include <stdlib.h> #include <stdio.h> @@ -5,7 +6,7 @@ struct Cache { int x; - Cache(int x) + explicit Cache(int x) : x(x) { } }; @@ -30,6 +31,7 @@ int main() { pthread_create(&t[1], 0, Thread1, 0); pthread_join(t[0], 0); pthread_join(t[1], 0); + printf("PASS\n"); } // CHECK-NOT: WARNING: ThreadSanitizer: data race diff --git a/lib/tsan/output_tests/static_init5.cc b/lib/tsan/lit_tests/static_init5.cc index 4b050c9fa69b..1d0ed6d54ca2 100644 --- a/lib/tsan/output_tests/static_init5.cc +++ b/lib/tsan/lit_tests/static_init5.cc @@ -1,3 +1,4 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s #include <pthread.h> #include <stdlib.h> #include <stdio.h> @@ -5,7 +6,7 @@ struct Cache { int x; - Cache(int x) + explicit Cache(int x) : x(x) { } }; @@ -16,7 +17,7 @@ void *AsyncInit(void *p) { Cache *CreateCache() { pthread_t t; - pthread_create(&t, 0, AsyncInit, (void*)rand()); + pthread_create(&t, 0, AsyncInit, (void*)(long)rand()); void *res; pthread_join(t, &res); return (Cache*)res; @@ -35,6 +36,7 @@ int main() { pthread_create(&t[1], 0, Thread1, 0); pthread_join(t[0], 0); pthread_join(t[1], 0); + printf("PASS\n"); } // CHECK-NOT: WARNING: ThreadSanitizer: data race diff --git a/lib/tsan/lit_tests/static_init6.cc b/lib/tsan/lit_tests/static_init6.cc new file mode 100644 index 000000000000..c9099f9b6790 --- /dev/null +++ b/lib/tsan/lit_tests/static_init6.cc @@ -0,0 +1,42 @@ +// RUN: %clangxx_tsan -static-libstdc++ -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdlib.h> +#include <stdio.h> +#include <sched.h> + +struct Cache { + int x; + explicit Cache(int x) + : x(x) { + } +}; + +void *AsyncInit(void *p) { + return new Cache((int)(long)p); +} + +Cache *CreateCache() { + pthread_t t; + pthread_create(&t, 0, AsyncInit, (void*)(long)rand()); + void *res; + pthread_join(t, &res); + return (Cache*)res; +} + +void *Thread1(void *x) { + static Cache *c = CreateCache(); + if (c->x >= RAND_MAX) + exit(1); + return 0; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], 0, Thread1, 0); + pthread_create(&t[1], 0, Thread1, 0); + pthread_join(t[0], 0); + pthread_join(t[1], 0); + printf("PASS\n"); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race diff --git a/lib/tsan/output_tests/suppress_same_address.cc b/lib/tsan/lit_tests/suppress_same_address.cc index 6e98970a16ef..174d1cc8fcb3 100644 --- a/lib/tsan/output_tests/suppress_same_address.cc +++ b/lib/tsan/lit_tests/suppress_same_address.cc @@ -1,3 +1,4 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s #include <pthread.h> int X; @@ -24,4 +25,3 @@ int main() { } // CHECK: ThreadSanitizer: reported 1 warnings - diff --git a/lib/tsan/output_tests/suppress_same_stacks.cc b/lib/tsan/lit_tests/suppress_same_stacks.cc index 6046a4ea9f3a..32bff9d50071 100644 --- a/lib/tsan/output_tests/suppress_same_stacks.cc +++ b/lib/tsan/lit_tests/suppress_same_stacks.cc @@ -1,3 +1,4 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s #include <pthread.h> volatile int N; // Prevent loop unrolling. @@ -24,4 +25,3 @@ int main() { } // CHECK: ThreadSanitizer: reported 1 warnings - diff --git a/lib/tsan/output_tests/test_output.sh b/lib/tsan/lit_tests/test_output.sh index bd9cd9158763..d21c9a797ad3 100755 --- a/lib/tsan/output_tests/test_output.sh +++ b/lib/tsan/lit_tests/test_output.sh @@ -4,13 +4,14 @@ ulimit -s 8192 set -e # fail on any error ROOTDIR=$(dirname $0)/.. +BLACKLIST=$ROOTDIR/lit_tests/Helpers/blacklist.txt # Assuming clang is in path. CC=clang CXX=clang++ # TODO: add testing for all of -O0...-O3 -CFLAGS="-fthread-sanitizer -fPIE -O1 -g -fno-builtin -Wall" +CFLAGS="-fsanitize=thread -fsanitize-blacklist=$BLACKLIST -fPIE -O1 -g -fno-builtin -Wall" LDFLAGS="-pie -lpthread -ldl $ROOTDIR/rtl/libtsan.a" test_file() { @@ -21,10 +22,7 @@ test_file() { EXE=$SRC.exe $COMPILER $SRC $CFLAGS -c -o $OBJ $COMPILER $OBJ $LDFLAGS -o $EXE - RES=$(TSAN_OPTIONS="atexit_sleep_ms=0" $EXE 2>&1 || true) - if [ "$3" != "" ]; then - printf "%s\n" "$RES" - fi + RES=$($EXE 2>&1 || true) printf "%s\n" "$RES" | FileCheck $SRC if [ "$3" == "" ]; then rm -f $EXE $OBJ @@ -32,7 +30,7 @@ test_file() { } if [ "$1" == "" ]; then - for c in $ROOTDIR/output_tests/*.{c,cc}; do + for c in $ROOTDIR/lit_tests/*.{c,cc}; do if [[ $c == */failing_* ]]; then echo SKIPPING FAILING TEST $c continue @@ -41,9 +39,11 @@ if [ "$1" == "" ]; then case $c in *.c) COMPILER=$CC esac - test_file $c $COMPILER + test_file $c $COMPILER & + done + for job in `jobs -p`; do + wait $job || exit 1 done - wait else - test_file $ROOTDIR/output_tests/$1 $CXX "DUMP" + test_file $ROOTDIR/lit_tests/$1 $CXX "DUMP" fi diff --git a/lib/tsan/output_tests/thread_leak.c b/lib/tsan/lit_tests/thread_leak.c index 88a11be4e9c0..c5e669e5d99f 100644 --- a/lib/tsan/output_tests/thread_leak.c +++ b/lib/tsan/lit_tests/thread_leak.c @@ -1,4 +1,6 @@ +// RUN: %clang_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s #include <pthread.h> +#include <stdio.h> void *Thread(void *x) { return 0; @@ -8,8 +10,8 @@ int main() { pthread_t t; pthread_create(&t, 0, Thread, 0); pthread_join(t, 0); + printf("PASS\n"); return 0; } // CHECK-NOT: WARNING: ThreadSanitizer: thread leak - diff --git a/lib/tsan/output_tests/thread_leak2.c b/lib/tsan/lit_tests/thread_leak2.c index 71e9c50b8a2f..39f6b5e02e39 100644 --- a/lib/tsan/output_tests/thread_leak2.c +++ b/lib/tsan/lit_tests/thread_leak2.c @@ -1,4 +1,6 @@ +// RUN: %clang_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s #include <pthread.h> +#include <stdio.h> void *Thread(void *x) { return 0; @@ -8,8 +10,8 @@ int main() { pthread_t t; pthread_create(&t, 0, Thread, 0); pthread_detach(t); + printf("PASS\n"); return 0; } // CHECK-NOT: WARNING: ThreadSanitizer: thread leak - diff --git a/lib/tsan/output_tests/thread_leak3.c b/lib/tsan/lit_tests/thread_leak3.c index 058b6e54d9da..c48219fe73fa 100644 --- a/lib/tsan/output_tests/thread_leak3.c +++ b/lib/tsan/lit_tests/thread_leak3.c @@ -1,3 +1,4 @@ +// RUN: %clang_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s #include <pthread.h> void *Thread(void *x) { @@ -11,4 +12,3 @@ int main() { } // CHECK: WARNING: ThreadSanitizer: thread leak - diff --git a/lib/tsan/lit_tests/thread_name.cc b/lib/tsan/lit_tests/thread_name.cc new file mode 100644 index 000000000000..0ca0b1769976 --- /dev/null +++ b/lib/tsan/lit_tests/thread_name.cc @@ -0,0 +1,34 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stdio.h> +#include <unistd.h> + +extern "C" void AnnotateThreadName(const char *f, int l, const char *name); + +int Global; + +void *Thread1(void *x) { + sleep(1); + AnnotateThreadName(__FILE__, __LINE__, "Thread1"); + Global++; + return NULL; +} + +void *Thread2(void *x) { + pthread_setname_np(pthread_self(), "Thread2"); + Global--; + return NULL; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: Thread T1 'Thread1' +// CHECK: Thread T2 'Thread2' + diff --git a/lib/tsan/output_tests/tiny_race.c b/lib/tsan/lit_tests/tiny_race.c index 3a8d192a671a..44cc1332f2ab 100644 --- a/lib/tsan/output_tests/tiny_race.c +++ b/lib/tsan/lit_tests/tiny_race.c @@ -1,3 +1,4 @@ +// RUN: %clang_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s #include <pthread.h> int Global; void *Thread1(void *x) { diff --git a/lib/tsan/lit_tests/tls_race.cc b/lib/tsan/lit_tests/tls_race.cc new file mode 100644 index 000000000000..bed6aafaacfc --- /dev/null +++ b/lib/tsan/lit_tests/tls_race.cc @@ -0,0 +1,19 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stddef.h> + +void *Thread(void *a) { + *(int*)a = 43; + return 0; +} + +int main() { + static __thread int Var = 42; + pthread_t t; + pthread_create(&t, 0, Thread, &Var); + Var = 43; + pthread_join(t, 0); +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: Location is TLS of main thread. diff --git a/lib/tsan/lit_tests/tls_race2.cc b/lib/tsan/lit_tests/tls_race2.cc new file mode 100644 index 000000000000..110abaa6a9df --- /dev/null +++ b/lib/tsan/lit_tests/tls_race2.cc @@ -0,0 +1,28 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <stddef.h> +#include <unistd.h> + +void *Thread2(void *a) { + *(int*)a = 43; + return 0; +} + +void *Thread(void *a) { + static __thread int Var = 42; + pthread_t t; + pthread_create(&t, 0, Thread2, &Var); + Var = 42; + pthread_join(t, 0); + return 0; +} + +int main() { + pthread_t t; + pthread_create(&t, 0, Thread, 0); + pthread_join(t, 0); +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: Location is TLS of thread T1. + diff --git a/lib/tsan/lit_tests/user_fopen.cc b/lib/tsan/lit_tests/user_fopen.cc new file mode 100644 index 000000000000..794d598719b4 --- /dev/null +++ b/lib/tsan/lit_tests/user_fopen.cc @@ -0,0 +1,34 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include <stdio.h> +#include <stdlib.h> + +// defined by tsan. +extern "C" FILE *__interceptor_fopen(const char *file, const char *mode); +extern "C" int __interceptor_fileno(FILE *f); + +extern "C" FILE *fopen(const char *file, const char *mode) { + static int first = 0; + if (__sync_lock_test_and_set(&first, 1) == 0) + printf("user fopen\n"); + return __interceptor_fopen(file, mode); +} + +extern "C" int fileno(FILE *f) { + static int first = 0; + if (__sync_lock_test_and_set(&first, 1) == 0) + printf("user fileno\n"); + return 1; +} + +int main() { + FILE *f = fopen("/dev/zero", "r"); + if (f) { + char buf; + fread(&buf, 1, 1, f); + fclose(f); + } +} + +// CHECK: user fopen +// CHECK-NOT: ThreadSanitizer + diff --git a/lib/tsan/lit_tests/user_malloc.cc b/lib/tsan/lit_tests/user_malloc.cc new file mode 100644 index 000000000000..0be6d54fb13a --- /dev/null +++ b/lib/tsan/lit_tests/user_malloc.cc @@ -0,0 +1,27 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include <stdio.h> + +// defined by tsan. +extern "C" void *__interceptor_malloc(unsigned long size); +extern "C" void __interceptor_free(void *p); + +extern "C" void *malloc(unsigned long size) { + static int first = 0; + if (__sync_lock_test_and_set(&first, 1) == 0) + printf("user malloc\n"); + return __interceptor_malloc(size); +} + +extern "C" void free(void *p) { + __interceptor_free(p); +} + +int main() { + volatile char *p = (char*)malloc(10); + p[0] = 0; + free((void*)p); +} + +// CHECK: user malloc +// CHECK-NOT: ThreadSanitizer + diff --git a/lib/tsan/output_tests/virtual_inheritance_compile_bug.cc b/lib/tsan/lit_tests/virtual_inheritance_compile_bug.cc index fd2febe895b4..2275b8b8d211 100644 --- a/lib/tsan/output_tests/virtual_inheritance_compile_bug.cc +++ b/lib/tsan/lit_tests/virtual_inheritance_compile_bug.cc @@ -1,10 +1,12 @@ // Regression test for http://code.google.com/p/thread-sanitizer/issues/detail?id=3. // The C++ variant is much more compact that the LLVM IR equivalent. + +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s #include <stdio.h> -struct AAA { virtual long aaa () { return 0; } }; -struct BBB: virtual AAA { unsigned long bbb; }; +struct AAA { virtual long aaa () { return 0; } }; // NOLINT +struct BBB: virtual AAA { unsigned long bbb; }; // NOLINT struct CCC: virtual AAA { }; -struct DDD: CCC, BBB { DDD (); }; +struct DDD: CCC, BBB { DDD(); }; // NOLINT DDD::DDD() { } int main() { DDD d; diff --git a/lib/tsan/output_tests/vptr_benign_race.cc b/lib/tsan/lit_tests/vptr_benign_race.cc index fec4ffbb6bc0..8c9fc596e17b 100644 --- a/lib/tsan/output_tests/vptr_benign_race.cc +++ b/lib/tsan/lit_tests/vptr_benign_race.cc @@ -1,3 +1,4 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s #include <pthread.h> #include <semaphore.h> #include <stdio.h> diff --git a/lib/tsan/output_tests/vptr_harmful_race.cc b/lib/tsan/lit_tests/vptr_harmful_race.cc index a19e6abc7bc3..f51ba7ee57f0 100644 --- a/lib/tsan/output_tests/vptr_harmful_race.cc +++ b/lib/tsan/lit_tests/vptr_harmful_race.cc @@ -1,3 +1,4 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s #include <pthread.h> #include <semaphore.h> #include <stdio.h> diff --git a/lib/tsan/lit_tests/write_in_reader_lock.cc b/lib/tsan/lit_tests/write_in_reader_lock.cc new file mode 100644 index 000000000000..db8bac32b6e4 --- /dev/null +++ b/lib/tsan/lit_tests/write_in_reader_lock.cc @@ -0,0 +1,35 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %t 2>&1 | FileCheck %s +#include <pthread.h> +#include <unistd.h> + +pthread_rwlock_t rwlock; +int GLOB; + +void *Thread1(void *p) { + (void)p; + pthread_rwlock_rdlock(&rwlock); + // Write under reader lock. + sleep(1); + GLOB++; + pthread_rwlock_unlock(&rwlock); + return 0; +} + +int main(int argc, char *argv[]) { + pthread_rwlock_init(&rwlock, NULL); + pthread_rwlock_rdlock(&rwlock); + pthread_t t; + pthread_create(&t, 0, Thread1, 0); + volatile int x = GLOB; + (void)x; + pthread_rwlock_unlock(&rwlock); + pthread_join(t, 0); + pthread_rwlock_destroy(&rwlock); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: Write of size 4 at {{.*}} by thread T1{{.*}}: +// CHECK: #0 Thread1(void*) {{.*}}write_in_reader_lock.cc:13 +// CHECK: Previous read of size 4 at {{.*}} by main thread{{.*}}: +// CHECK: #0 main {{.*}}write_in_reader_lock.cc:23 diff --git a/lib/tsan/output_tests/simple_stack.c b/lib/tsan/output_tests/simple_stack.c deleted file mode 100644 index 2e94f23f3c46..000000000000 --- a/lib/tsan/output_tests/simple_stack.c +++ /dev/null @@ -1,65 +0,0 @@ -#include <pthread.h> -#include <stdio.h> -#include <unistd.h> - -int Global; - -void __attribute__((noinline)) foo1() { - Global = 42; -} - -void __attribute__((noinline)) bar1() { - volatile int tmp = 42; (void)tmp; - foo1(); -} - -void __attribute__((noinline)) foo2() { - volatile int v = Global; (void)v; -} - -void __attribute__((noinline)) bar2() { - volatile int tmp = 42; (void)tmp; - foo2(); -} - -void *Thread1(void *x) { - usleep(1000000); - bar1(); - return NULL; -} - -void *Thread2(void *x) { - bar2(); - return NULL; -} - -void StartThread(pthread_t *t, void *(*f)(void*)) { - pthread_create(t, NULL, f, NULL); -} - -int main() { - pthread_t t[2]; - StartThread(&t[0], Thread1); - StartThread(&t[1], Thread2); - pthread_join(t[0], NULL); - pthread_join(t[1], NULL); - return 0; -} - -// CHECK: WARNING: ThreadSanitizer: data race -// CHECK-NEXT: Write of size 4 at {{.*}} by thread 1: -// CHECK-NEXT: #0 foo1 {{.*}}simple_stack.c:8{{(:3)?}} ({{.*}}) -// CHECK-NEXT: #1 bar1 {{.*}}simple_stack.c:13{{(:3)?}} ({{.*}}) -// CHECK-NEXT: #2 Thread1 {{.*}}simple_stack.c:27{{(:3)?}} ({{.*}}) -// CHECK-NEXT: Previous read of size 4 at {{.*}} by thread 2: -// CHECK-NEXT: #0 foo2 {{.*}}simple_stack.c:17{{(:26)?}} ({{.*}}) -// CHECK-NEXT: #1 bar2 {{.*}}simple_stack.c:22{{(:3)?}} ({{.*}}) -// CHECK-NEXT: #2 Thread2 {{.*}}simple_stack.c:32{{(:3)?}} ({{.*}}) -// CHECK-NEXT: Thread 1 (running) created at: -// CHECK-NEXT: #0 pthread_create {{.*}} ({{.*}}) -// CHECK-NEXT: #1 StartThread {{.*}}simple_stack.c:37{{(:3)?}} ({{.*}}) -// CHECK-NEXT: #2 main {{.*}}simple_stack.c:42{{(:3)?}} ({{.*}}) -// CHECK-NEXT: Thread 2 ({{.*}}) created at: -// CHECK-NEXT: #0 pthread_create {{.*}} ({{.*}}) -// CHECK-NEXT: #1 StartThread {{.*}}simple_stack.c:37{{(:3)?}} ({{.*}}) -// CHECK-NEXT: #2 main {{.*}}simple_stack.c:43{{(:3)?}} ({{.*}}) diff --git a/lib/tsan/output_tests/simple_stack2.cc b/lib/tsan/output_tests/simple_stack2.cc deleted file mode 100644 index 336cc9ff599d..000000000000 --- a/lib/tsan/output_tests/simple_stack2.cc +++ /dev/null @@ -1,46 +0,0 @@ -#include <pthread.h> -#include <stdio.h> -#include <unistd.h> - -int Global; - -void __attribute__((noinline)) foo1() { - Global = 42; -} - -void __attribute__((noinline)) bar1() { - volatile int tmp = 42; int tmp2 = tmp; (void)tmp2; - foo1(); -} - -void __attribute__((noinline)) foo2() { - volatile int tmp = Global; int tmp2 = tmp; (void)tmp2; -} - -void __attribute__((noinline)) bar2() { - volatile int tmp = 42; int tmp2 = tmp; (void)tmp2; - foo2(); -} - -void *Thread1(void *x) { - usleep(1000000); - bar1(); - return NULL; -} - -int main() { - pthread_t t; - pthread_create(&t, NULL, Thread1, NULL); - bar2(); - pthread_join(t, NULL); -} - -// CHECK: WARNING: ThreadSanitizer: data race -// CHECK-NEXT: Write of size 4 at {{.*}} by thread 1: -// CHECK-NEXT: #0 foo1{{.*}} {{.*}}simple_stack2.cc:8{{(:3)?}} ({{.*}}) -// CHECK-NEXT: #1 bar1{{.*}} {{.*}}simple_stack2.cc:13{{(:3)?}} ({{.*}}) -// CHECK-NEXT: #2 Thread1{{.*}} {{.*}}simple_stack2.cc:27{{(:3)?}} ({{.*}}) -// CHECK-NEXT: Previous read of size 4 at {{.*}} by main thread: -// CHECK-NEXT: #0 foo2{{.*}} {{.*}}simple_stack2.cc:17{{(:28)?}} ({{.*}}) -// CHECK-NEXT: #1 bar2{{.*}} {{.*}}simple_stack2.cc:22{{(:3)?}} ({{.*}}) -// CHECK-NEXT: #2 main{{.*}} {{.*}}simple_stack2.cc:34{{(:3)?}} ({{.*}}) diff --git a/lib/tsan/rtl/CMakeLists.txt b/lib/tsan/rtl/CMakeLists.txt new file mode 100644 index 000000000000..d91e2e43ca4c --- /dev/null +++ b/lib/tsan/rtl/CMakeLists.txt @@ -0,0 +1,58 @@ +set(TSAN_SOURCES + tsan_clock.cc + tsan_flags.cc + tsan_fd.cc + tsan_interceptors.cc + tsan_interface_ann.cc + tsan_interface_atomic.cc + tsan_interface.cc + tsan_interface_java.cc + tsan_md5.cc + tsan_mman.cc + tsan_mutex.cc + tsan_mutexset.cc + tsan_report.cc + tsan_rtl.cc + tsan_rtl_mutex.cc + tsan_rtl_report.cc + tsan_rtl_thread.cc + tsan_stat.cc + tsan_suppressions.cc + tsan_symbolize.cc + tsan_sync.cc + ) + +if(APPLE) + list(APPEND TSAN_SOURCES tsan_platform_mac.cc) +elseif(UNIX) + # Assume Linux + list(APPEND TSAN_SOURCES + tsan_platform_linux.cc + tsan_symbolize_addr2line_linux.cc) +endif() + +set(TSAN_RUNTIME_LIBRARIES) +# TSan is currently supported on 64-bit Linux only. +if(CAN_TARGET_x86_64 AND UNIX AND NOT APPLE) + set(TSAN_ASM_SOURCES tsan_rtl_amd64.S) + # Pass ASM file directly to the C++ compiler. + set_source_files_properties(${TSAN_ASM_SOURCES} PROPERTIES + LANGUAGE C + ) + add_library(clang_rt.tsan-x86_64 STATIC + ${TSAN_SOURCES} + ${TSAN_ASM_SOURCES} + $<TARGET_OBJECTS:RTInterception.x86_64> + $<TARGET_OBJECTS:RTSanitizerCommon.x86_64> + ) + set_target_compile_flags(clang_rt.tsan-x86_64 + ${TSAN_CFLAGS} ${TARGET_x86_64_CFLAGS} + ) + list(APPEND TSAN_RUNTIME_LIBRARIES clang_rt.tsan-x86_64) +endif() + +if(TSAN_RUNTIME_LIBRARIES) + set_property(TARGET ${TSAN_RUNTIME_LIBRARIES} APPEND PROPERTY + COMPILE_DEFINITIONS ${TSAN_COMMON_DEFINITIONS}) + add_clang_compiler_rt_libraries(${TSAN_RUNTIME_LIBRARIES}) +endif() diff --git a/lib/tsan/rtl/Makefile.mk b/lib/tsan/rtl/Makefile.mk index d5d6327b5435..a6a7fc8b86e8 100644 --- a/lib/tsan/rtl/Makefile.mk +++ b/lib/tsan/rtl/Makefile.mk @@ -18,6 +18,8 @@ Implementation := Generic # FIXME: use automatic dependencies? Dependencies := $(wildcard $(Dir)/*.h) +Dependencies += $(wildcard $(Dir)/../../interception/*.h) +Dependencies += $(wildcard $(Dir)/../../interception/mach_override/*.h) # Define a convenience variable for all the tsan functions. TsanFunctions += $(Sources:%.cc=%) $(AsmSources:%.S=%) diff --git a/lib/tsan/rtl/Makefile.old b/lib/tsan/rtl/Makefile.old index 9b79f576d4aa..f522ec6b47d7 100644 --- a/lib/tsan/rtl/Makefile.old +++ b/lib/tsan/rtl/Makefile.old @@ -1,12 +1,15 @@ -CXXFLAGS = -fPIE -g -Wall -Werror -fno-builtin -DTSAN_DEBUG=$(DEBUG) +CXXFLAGS = -fPIE -g -Wall -Werror -fno-builtin -DTSAN_DEBUG=$(DEBUG) -DSANITIZER_DEBUG=$(DEBUG) ifeq ($(DEBUG), 0) - CXXFLAGS += -O3 + CXXFLAGS += -O3 +endif +ifeq ($(CXX), clang++) + CXXFLAGS+= -Wgnu endif # For interception. FIXME: move interception one level higher. INTERCEPTION=../../interception COMMON=../../sanitizer_common -INCLUDES= -I../.. +INCLUDES= -I../.. -I../../../include EXTRA_CXXFLAGS=-fno-exceptions NO_SYSROOT=--sysroot=. CXXFLAGS+=$(EXTRA_CXXFLAGS) diff --git a/lib/tsan/rtl/tsan_clock.cc b/lib/tsan/rtl/tsan_clock.cc index 32ed91dc2103..f8745ec3200c 100644 --- a/lib/tsan/rtl/tsan_clock.cc +++ b/lib/tsan/rtl/tsan_clock.cc @@ -105,13 +105,6 @@ void ThreadClock::acq_rel(SyncClock *dst) { release(dst); } -void ThreadClock::Disable(unsigned tid) { - u64 c0 = clk_[tid]; - for (uptr i = 0; i < kMaxTidInClock; i++) - clk_[i] = (u64)-1; - clk_[tid] = c0; -} - SyncClock::SyncClock() : clk_(MBlockClock) { } diff --git a/lib/tsan/rtl/tsan_clock.h b/lib/tsan/rtl/tsan_clock.h index 02ddb9abdb30..0ee93749b881 100644 --- a/lib/tsan/rtl/tsan_clock.h +++ b/lib/tsan/rtl/tsan_clock.h @@ -61,8 +61,6 @@ struct ThreadClock { nclk_ = tid + 1; } - void Disable(unsigned tid); - uptr size() const { return nclk_; } diff --git a/lib/tsan/rtl/tsan_defs.h b/lib/tsan/rtl/tsan_defs.h index ca8f0aecc83a..e0c04733f0a3 100644 --- a/lib/tsan/rtl/tsan_defs.h +++ b/lib/tsan/rtl/tsan_defs.h @@ -24,31 +24,45 @@ namespace __tsan { +#ifdef TSAN_GO +const bool kGoMode = true; +const bool kCppMode = false; +const char *const kTsanOptionsEnv = "GORACE"; +#else +const bool kGoMode = false; +const bool kCppMode = true; +const char *const kTsanOptionsEnv = "TSAN_OPTIONS"; +#endif + const int kTidBits = 13; const unsigned kMaxTid = 1 << kTidBits; const unsigned kMaxTidInClock = kMaxTid * 2; // This includes msb 'freed' bit. const int kClkBits = 43; #ifndef TSAN_GO -const int kShadowStackSize = 1024; +const int kShadowStackSize = 4 * 1024; +const int kTraceStackSize = 256; #endif #ifdef TSAN_SHADOW_COUNT # if TSAN_SHADOW_COUNT == 2 \ || TSAN_SHADOW_COUNT == 4 || TSAN_SHADOW_COUNT == 8 -const unsigned kShadowCnt = TSAN_SHADOW_COUNT; +const uptr kShadowCnt = TSAN_SHADOW_COUNT; # else # error "TSAN_SHADOW_COUNT must be one of 2,4,8" # endif #else // Count of shadow values in a shadow cell. -const unsigned kShadowCnt = 8; +const uptr kShadowCnt = 4; #endif // That many user bytes are mapped onto a single shadow cell. -const unsigned kShadowCell = 8; +const uptr kShadowCell = 8; // Size of a single shadow value (u64). -const unsigned kShadowSize = 8; +const uptr kShadowSize = 8; + +// Shadow memory is kShadowMultiplier times larger than user memory. +const uptr kShadowMultiplier = kShadowSize * kShadowCnt / kShadowCell; #if defined(TSAN_COLLECT_STATS) && TSAN_COLLECT_STATS const bool kCollectStats = true; @@ -114,11 +128,23 @@ T max(T a, T b) { } template<typename T> -T RoundUp(T p, int align) { +T RoundUp(T p, u64 align) { DCHECK_EQ(align & (align - 1), 0); return (T)(((u64)p + align - 1) & ~(align - 1)); } +template<typename T> +T RoundDown(T p, u64 align) { + DCHECK_EQ(align & (align - 1), 0); + return (T)((u64)p & ~(align - 1)); +} + +// Zeroizes high part, returns 'bits' lsb bits. +template<typename T> +T GetLsb(T v, int bits) { + return (T)((u64)v & ((1ull << bits) - 1)); +} + struct MD5Hash { u64 hash[2]; bool operator==(const MD5Hash &other) const; @@ -133,6 +159,7 @@ struct ReportStack; class ReportDesc; class RegionAlloc; class StackTrace; +struct MBlock; } // namespace __tsan diff --git a/lib/tsan/rtl/tsan_fd.cc b/lib/tsan/rtl/tsan_fd.cc new file mode 100644 index 000000000000..ef375a4d98f6 --- /dev/null +++ b/lib/tsan/rtl/tsan_fd.cc @@ -0,0 +1,265 @@ +//===-- tsan_fd.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 (TSan), a race detector. +// +//===----------------------------------------------------------------------===// + +#include "tsan_fd.h" +#include "tsan_rtl.h" +#include <sanitizer_common/sanitizer_atomic.h> + +namespace __tsan { + +const int kTableSizeL1 = 1024; +const int kTableSizeL2 = 1024; +const int kTableSize = kTableSizeL1 * kTableSizeL2; + +struct FdSync { + atomic_uint64_t rc; +}; + +struct FdDesc { + FdSync *sync; + int creation_tid; + u32 creation_stack; +}; + +struct FdContext { + atomic_uintptr_t tab[kTableSizeL1]; + // Addresses used for synchronization. + FdSync globsync; + FdSync filesync; + FdSync socksync; + u64 connectsync; +}; + +static FdContext fdctx; + +static FdSync *allocsync() { + FdSync *s = (FdSync*)internal_alloc(MBlockFD, sizeof(FdSync)); + atomic_store(&s->rc, 1, memory_order_relaxed); + return s; +} + +static FdSync *ref(FdSync *s) { + if (s && atomic_load(&s->rc, memory_order_relaxed) != (u64)-1) + atomic_fetch_add(&s->rc, 1, memory_order_relaxed); + return s; +} + +static void unref(ThreadState *thr, uptr pc, FdSync *s) { + if (s && atomic_load(&s->rc, memory_order_relaxed) != (u64)-1) { + if (atomic_fetch_sub(&s->rc, 1, memory_order_acq_rel) == 1) { + CHECK_NE(s, &fdctx.globsync); + CHECK_NE(s, &fdctx.filesync); + CHECK_NE(s, &fdctx.socksync); + SyncVar *v = CTX()->synctab.GetAndRemove(thr, pc, (uptr)s); + if (v) + DestroyAndFree(v); + internal_free(s); + } + } +} + +static FdDesc *fddesc(ThreadState *thr, uptr pc, int fd) { + CHECK_LT(fd, kTableSize); + atomic_uintptr_t *pl1 = &fdctx.tab[fd / kTableSizeL2]; + uptr l1 = atomic_load(pl1, memory_order_consume); + if (l1 == 0) { + uptr size = kTableSizeL2 * sizeof(FdDesc); + void *p = internal_alloc(MBlockFD, size); + internal_memset(p, 0, size); + MemoryResetRange(thr, (uptr)&fddesc, (uptr)p, size); + if (atomic_compare_exchange_strong(pl1, &l1, (uptr)p, memory_order_acq_rel)) + l1 = (uptr)p; + else + internal_free(p); + } + return &((FdDesc*)l1)[fd % kTableSizeL2]; // NOLINT +} + +// pd must be already ref'ed. +static void init(ThreadState *thr, uptr pc, int fd, FdSync *s) { + FdDesc *d = fddesc(thr, pc, fd); + // As a matter of fact, we don't intercept all close calls. + // See e.g. libc __res_iclose(). + if (d->sync) { + unref(thr, pc, d->sync); + d->sync = 0; + } + if (flags()->io_sync == 0) { + unref(thr, pc, s); + } else if (flags()->io_sync == 1) { + d->sync = s; + } else if (flags()->io_sync == 2) { + unref(thr, pc, s); + d->sync = &fdctx.globsync; + } + d->creation_tid = thr->tid; + d->creation_stack = CurrentStackId(thr, pc); + // To catch races between fd usage and open. + MemoryRangeImitateWrite(thr, pc, (uptr)d, 8); +} + +void FdInit() { + atomic_store(&fdctx.globsync.rc, (u64)-1, memory_order_relaxed); + atomic_store(&fdctx.filesync.rc, (u64)-1, memory_order_relaxed); + atomic_store(&fdctx.socksync.rc, (u64)-1, memory_order_relaxed); +} + +void FdOnFork(ThreadState *thr, uptr pc) { + // On fork() we need to reset all fd's, because the child is going + // close all them, and that will cause races between previous read/write + // and the close. + for (int l1 = 0; l1 < kTableSizeL1; l1++) { + FdDesc *tab = (FdDesc*)atomic_load(&fdctx.tab[l1], memory_order_relaxed); + if (tab == 0) + break; + for (int l2 = 0; l2 < kTableSizeL2; l2++) { + FdDesc *d = &tab[l2]; + MemoryResetRange(thr, pc, (uptr)d, 8); + } + } +} + +bool FdLocation(uptr addr, int *fd, int *tid, u32 *stack) { + for (int l1 = 0; l1 < kTableSizeL1; l1++) { + FdDesc *tab = (FdDesc*)atomic_load(&fdctx.tab[l1], memory_order_relaxed); + if (tab == 0) + break; + if (addr >= (uptr)tab && addr < (uptr)(tab + kTableSizeL2)) { + int l2 = (addr - (uptr)tab) / sizeof(FdDesc); + FdDesc *d = &tab[l2]; + *fd = l1 * kTableSizeL1 + l2; + *tid = d->creation_tid; + *stack = d->creation_stack; + return true; + } + } + return false; +} + +void FdAcquire(ThreadState *thr, uptr pc, int fd) { + FdDesc *d = fddesc(thr, pc, fd); + FdSync *s = d->sync; + DPrintf("#%d: FdAcquire(%d) -> %p\n", thr->tid, fd, s); + MemoryRead8Byte(thr, pc, (uptr)d); + if (s) + Acquire(thr, pc, (uptr)s); +} + +void FdRelease(ThreadState *thr, uptr pc, int fd) { + FdDesc *d = fddesc(thr, pc, fd); + FdSync *s = d->sync; + DPrintf("#%d: FdRelease(%d) -> %p\n", thr->tid, fd, s); + if (s) + Release(thr, pc, (uptr)s); + MemoryRead8Byte(thr, pc, (uptr)d); +} + +void FdAccess(ThreadState *thr, uptr pc, int fd) { + DPrintf("#%d: FdAccess(%d)\n", thr->tid, fd); + FdDesc *d = fddesc(thr, pc, fd); + MemoryRead8Byte(thr, pc, (uptr)d); +} + +void FdClose(ThreadState *thr, uptr pc, int fd) { + DPrintf("#%d: FdClose(%d)\n", thr->tid, fd); + FdDesc *d = fddesc(thr, pc, fd); + // To catch races between fd usage and close. + MemoryWrite8Byte(thr, pc, (uptr)d); + // We need to clear it, because if we do not intercept any call out there + // that creates fd, we will hit false postives. + MemoryResetRange(thr, pc, (uptr)d, 8); + unref(thr, pc, d->sync); + d->sync = 0; + d->creation_tid = 0; + d->creation_stack = 0; +} + +void FdFileCreate(ThreadState *thr, uptr pc, int fd) { + DPrintf("#%d: FdFileCreate(%d)\n", thr->tid, fd); + init(thr, pc, fd, &fdctx.filesync); +} + +void FdDup(ThreadState *thr, uptr pc, int oldfd, int newfd) { + DPrintf("#%d: FdDup(%d, %d)\n", thr->tid, oldfd, newfd); + // Ignore the case when user dups not yet connected socket. + FdDesc *od = fddesc(thr, pc, oldfd); + MemoryRead8Byte(thr, pc, (uptr)od); + FdClose(thr, pc, newfd); + init(thr, pc, newfd, ref(od->sync)); +} + +void FdPipeCreate(ThreadState *thr, uptr pc, int rfd, int wfd) { + DPrintf("#%d: FdCreatePipe(%d, %d)\n", thr->tid, rfd, wfd); + FdSync *s = allocsync(); + init(thr, pc, rfd, ref(s)); + init(thr, pc, wfd, ref(s)); + unref(thr, pc, s); +} + +void FdEventCreate(ThreadState *thr, uptr pc, int fd) { + DPrintf("#%d: FdEventCreate(%d)\n", thr->tid, fd); + init(thr, pc, fd, allocsync()); +} + +void FdSignalCreate(ThreadState *thr, uptr pc, int fd) { + DPrintf("#%d: FdSignalCreate(%d)\n", thr->tid, fd); + init(thr, pc, fd, 0); +} + +void FdInotifyCreate(ThreadState *thr, uptr pc, int fd) { + DPrintf("#%d: FdInotifyCreate(%d)\n", thr->tid, fd); + init(thr, pc, fd, 0); +} + +void FdPollCreate(ThreadState *thr, uptr pc, int fd) { + DPrintf("#%d: FdPollCreate(%d)\n", thr->tid, fd); + init(thr, pc, fd, allocsync()); +} + +void FdSocketCreate(ThreadState *thr, uptr pc, int fd) { + DPrintf("#%d: FdSocketCreate(%d)\n", thr->tid, fd); + // It can be a UDP socket. + init(thr, pc, fd, &fdctx.socksync); +} + +void FdSocketAccept(ThreadState *thr, uptr pc, int fd, int newfd) { + DPrintf("#%d: FdSocketAccept(%d, %d)\n", thr->tid, fd, newfd); + // Synchronize connect->accept. + Acquire(thr, pc, (uptr)&fdctx.connectsync); + init(thr, pc, newfd, &fdctx.socksync); +} + +void FdSocketConnecting(ThreadState *thr, uptr pc, int fd) { + DPrintf("#%d: FdSocketConnecting(%d)\n", thr->tid, fd); + // Synchronize connect->accept. + Release(thr, pc, (uptr)&fdctx.connectsync); +} + +void FdSocketConnect(ThreadState *thr, uptr pc, int fd) { + DPrintf("#%d: FdSocketConnect(%d)\n", thr->tid, fd); + init(thr, pc, fd, &fdctx.socksync); +} + +uptr File2addr(char *path) { + (void)path; + static u64 addr; + return (uptr)&addr; +} + +uptr Dir2addr(char *path) { + (void)path; + static u64 addr; + return (uptr)&addr; +} + +} // namespace __tsan diff --git a/lib/tsan/rtl/tsan_fd.h b/lib/tsan/rtl/tsan_fd.h new file mode 100644 index 000000000000..979198e2e74d --- /dev/null +++ b/lib/tsan/rtl/tsan_fd.h @@ -0,0 +1,65 @@ +//===-- tsan_fd.h -----------------------------------------------*- C++ -*-===// +// +// 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 (TSan), a race detector. +// +// This file handles synchronization via IO. +// People use IO for synchronization along the lines of: +// +// int X; +// int client_socket; // initialized elsewhere +// int server_socket; // initialized elsewhere +// +// Thread 1: +// X = 42; +// send(client_socket, ...); +// +// Thread 2: +// if (recv(server_socket, ...) > 0) +// assert(X == 42); +// +// This file determines the scope of the file descriptor (pipe, socket, +// all local files, etc) and executes acquire and release operations on +// the scope as necessary. Some scopes are very fine grained (e.g. pipe +// operations synchronize only with operations on the same pipe), while +// others are corse-grained (e.g. all operations on local files synchronize +// with each other). +//===----------------------------------------------------------------------===// +#ifndef TSAN_FD_H +#define TSAN_FD_H + +#include "tsan_rtl.h" + +namespace __tsan { + +void FdInit(); +void FdAcquire(ThreadState *thr, uptr pc, int fd); +void FdRelease(ThreadState *thr, uptr pc, int fd); +void FdAccess(ThreadState *thr, uptr pc, int fd); +void FdClose(ThreadState *thr, uptr pc, int fd); +void FdFileCreate(ThreadState *thr, uptr pc, int fd); +void FdDup(ThreadState *thr, uptr pc, int oldfd, int newfd); +void FdPipeCreate(ThreadState *thr, uptr pc, int rfd, int wfd); +void FdEventCreate(ThreadState *thr, uptr pc, int fd); +void FdSignalCreate(ThreadState *thr, uptr pc, int fd); +void FdInotifyCreate(ThreadState *thr, uptr pc, int fd); +void FdPollCreate(ThreadState *thr, uptr pc, int fd); +void FdSocketCreate(ThreadState *thr, uptr pc, int fd); +void FdSocketAccept(ThreadState *thr, uptr pc, int fd, int newfd); +void FdSocketConnecting(ThreadState *thr, uptr pc, int fd); +void FdSocketConnect(ThreadState *thr, uptr pc, int fd); +bool FdLocation(uptr addr, int *fd, int *tid, u32 *stack); +void FdOnFork(ThreadState *thr, uptr pc); + +uptr File2addr(char *path); +uptr Dir2addr(char *path); + +} // namespace __tsan + +#endif // TSAN_INTERFACE_H diff --git a/lib/tsan/rtl/tsan_flags.cc b/lib/tsan/rtl/tsan_flags.cc index 8f91939db1de..88c4bb6a2e44 100644 --- a/lib/tsan/rtl/tsan_flags.cc +++ b/lib/tsan/rtl/tsan_flags.cc @@ -27,6 +27,7 @@ Flags *flags() { #ifdef TSAN_EXTERNAL_HOOKS void OverrideFlags(Flags *f); #else +SANITIZER_INTERFACE_ATTRIBUTE void WEAK OverrideFlags(Flags *f) { (void)f; } @@ -39,20 +40,25 @@ void InitializeFlags(Flags *f, const char *env) { f->enable_annotations = true; f->suppress_equal_stacks = true; f->suppress_equal_addresses = true; + f->suppress_java = false; + f->report_bugs = true; f->report_thread_leaks = true; + f->report_destroy_locked = true; f->report_signal_unsafe = true; f->force_seq_cst_atomics = false; f->strip_path_prefix = ""; f->suppressions = ""; f->exitcode = 66; - f->log_fileno = 2; + f->log_path = "stderr"; f->atexit_sleep_ms = 1000; f->verbosity = 0; f->profile_memory = ""; f->flush_memory_ms = 0; f->stop_on_start = false; f->running_on_valgrind = false; - f->use_internal_symbolizer = false; + f->external_symbolizer_path = ""; + f->history_size = kGoMode ? 1 : 2; // There are a lot of goroutines in Go. + f->io_sync = 1; // Let a frontend override. OverrideFlags(f); @@ -61,19 +67,42 @@ void InitializeFlags(Flags *f, const char *env) { ParseFlag(env, &f->enable_annotations, "enable_annotations"); ParseFlag(env, &f->suppress_equal_stacks, "suppress_equal_stacks"); ParseFlag(env, &f->suppress_equal_addresses, "suppress_equal_addresses"); + ParseFlag(env, &f->suppress_java, "suppress_java"); + ParseFlag(env, &f->report_bugs, "report_bugs"); ParseFlag(env, &f->report_thread_leaks, "report_thread_leaks"); + ParseFlag(env, &f->report_destroy_locked, "report_destroy_locked"); ParseFlag(env, &f->report_signal_unsafe, "report_signal_unsafe"); ParseFlag(env, &f->force_seq_cst_atomics, "force_seq_cst_atomics"); ParseFlag(env, &f->strip_path_prefix, "strip_path_prefix"); ParseFlag(env, &f->suppressions, "suppressions"); ParseFlag(env, &f->exitcode, "exitcode"); - ParseFlag(env, &f->log_fileno, "log_fileno"); + ParseFlag(env, &f->log_path, "log_path"); ParseFlag(env, &f->atexit_sleep_ms, "atexit_sleep_ms"); ParseFlag(env, &f->verbosity, "verbosity"); ParseFlag(env, &f->profile_memory, "profile_memory"); ParseFlag(env, &f->flush_memory_ms, "flush_memory_ms"); ParseFlag(env, &f->stop_on_start, "stop_on_start"); - ParseFlag(env, &f->use_internal_symbolizer, "use_internal_symbolizer"); + ParseFlag(env, &f->external_symbolizer_path, "external_symbolizer_path"); + ParseFlag(env, &f->history_size, "history_size"); + ParseFlag(env, &f->io_sync, "io_sync"); + + if (!f->report_bugs) { + f->report_thread_leaks = false; + f->report_destroy_locked = false; + f->report_signal_unsafe = false; + } + + if (f->history_size < 0 || f->history_size > 7) { + Printf("ThreadSanitizer: incorrect value for history_size" + " (must be [0..7])\n"); + Die(); + } + + if (f->io_sync < 0 || f->io_sync > 2) { + Printf("ThreadSanitizer: incorrect value for io_sync" + " (must be [0..2])\n"); + Die(); + } } } // namespace __tsan diff --git a/lib/tsan/rtl/tsan_flags.h b/lib/tsan/rtl/tsan_flags.h index c22132f2d32f..6547911ec7a3 100644 --- a/lib/tsan/rtl/tsan_flags.h +++ b/lib/tsan/rtl/tsan_flags.h @@ -31,8 +31,15 @@ struct Flags { // Supress a race report if we've already output another race report // on the same address. bool suppress_equal_addresses; + // Suppress weird race reports that can be seen if JVM is embed + // into the process. + bool suppress_java; + // Turns off bug reporting entirely (useful for benchmarking). + bool report_bugs; // Report thread leaks at exit? bool report_thread_leaks; + // Report destruction of a locked mutex? + bool report_destroy_locked; // Report violations of async signal-safety // (e.g. malloc() call from a signal handler). bool report_signal_unsafe; @@ -45,8 +52,10 @@ struct Flags { const char *suppressions; // Override exit status if something was reported. int exitcode; - // Log fileno (1 - stdout, 2 - stderr). - int log_fileno; + // Write logs to "log_path.pid". + // The special values are "stdout" and "stderr". + // The default is "stderr". + const char *log_path; // Sleep in main thread before exiting for that many ms // (useful to catch "at exit" races). int atexit_sleep_ms; @@ -60,8 +69,19 @@ struct Flags { bool stop_on_start; // Controls whether RunningOnValgrind() returns true or false. bool running_on_valgrind; - // If set, uses in-process symbolizer from common sanitizer runtime. - bool use_internal_symbolizer; + // Path to external symbolizer. + const char *external_symbolizer_path; + // Per-thread history size, controls how many previous memory accesses + // are remembered per thread. Possible values are [0..7]. + // history_size=0 amounts to 32K memory accesses. Each next value doubles + // the amount of memory accesses, up to history_size=7 that amounts to + // 4M memory accesses. The default value is 2 (128K memory accesses). + int history_size; + // Controls level of synchronization implied by IO operations. + // 0 - no synchronization + // 1 - reasonable level of synchronization (write->read) + // 2 - global synchronization of all IO operations + int io_sync; }; Flags *flags(); diff --git a/lib/tsan/rtl/tsan_interceptors.cc b/lib/tsan/rtl/tsan_interceptors.cc index a962250568b7..be58ca92cf91 100644 --- a/lib/tsan/rtl/tsan_interceptors.cc +++ b/lib/tsan/rtl/tsan_interceptors.cc @@ -1,4 +1,4 @@ -//===-- tsan_interceptors_linux.cc ----------------------------------------===// +//===-- tsan_interceptors.cc ----------------------------------------------===// // // The LLVM Compiler Infrastructure // @@ -9,16 +9,20 @@ // // This file is a part of ThreadSanitizer (TSan), a race detector. // +// FIXME: move as many interceptors as possible into +// sanitizer_common/sanitizer_common_interceptors.h //===----------------------------------------------------------------------===// -#include "interception/interception.h" #include "sanitizer_common/sanitizer_atomic.h" #include "sanitizer_common/sanitizer_libc.h" #include "sanitizer_common/sanitizer_placement_new.h" -#include "tsan_rtl.h" +#include "sanitizer_common/sanitizer_stacktrace.h" +#include "interception/interception.h" #include "tsan_interface.h" #include "tsan_platform.h" +#include "tsan_rtl.h" #include "tsan_mman.h" +#include "tsan_fd.h" using namespace __tsan; // NOLINT @@ -51,7 +55,7 @@ extern "C" void *pthread_self(); extern "C" void _exit(int status); extern "C" int __cxa_atexit(void (*func)(void *arg), void *arg, void *dso); extern "C" int *__errno_location(); -extern "C" int usleep(unsigned usec); +extern "C" int fileno_unlocked(void *stream); const int PTHREAD_MUTEX_RECURSIVE = 1; const int PTHREAD_MUTEX_RECURSIVE_NP = 1; const int kPthreadAttrSize = 56; @@ -69,6 +73,12 @@ const int PTHREAD_BARRIER_SERIAL_THREAD = -1; const int MAP_FIXED = 0x10; typedef long long_t; // NOLINT +// From /usr/include/unistd.h +# define F_ULOCK 0 /* Unlock a previously locked region. */ +# define F_LOCK 1 /* Lock a region for exclusive use. */ +# define F_TLOCK 2 /* Test and lock a region for exclusive use. */ +# define F_TEST 3 /* Test a region for other processes locks. */ + typedef void (*sighandler_t)(int sig); #define errno (*__errno_location()) @@ -94,6 +104,10 @@ const sighandler_t SIG_ERR = (sighandler_t)-1; const int SA_SIGINFO = 4; const int SIG_SETMASK = 2; +namespace std { +struct nothrow_t {}; +} // namespace std + static sigaction_t sigactions[kSigCount]; namespace __tsan { @@ -105,6 +119,7 @@ struct SignalDesc { }; struct SignalContext { + int in_blocking_func; int int_signal_send; int pending_signal_count; SignalDesc pending_signals[kSigCount]; @@ -115,10 +130,8 @@ static SignalContext *SigCtx(ThreadState *thr) { SignalContext *ctx = (SignalContext*)thr->signal_ctx; if (ctx == 0 && thr->is_alive) { ScopedInRtl in_rtl; - ctx = (SignalContext*)internal_alloc( - MBlockSignal, sizeof(*ctx)); - MemoryResetRange(thr, 0, (uptr)ctx, sizeof(*ctx)); - internal_memset(ctx, 0, sizeof(*ctx)); + ctx = (SignalContext*)MmapOrDie(sizeof(*ctx), "SignalContext"); + MemoryResetRange(thr, (uptr)&SigCtx, (uptr)ctx, sizeof(*ctx)); thr->signal_ctx = ctx; } return ctx; @@ -126,60 +139,55 @@ static SignalContext *SigCtx(ThreadState *thr) { static unsigned g_thread_finalize_key; -static void process_pending_signals(ThreadState *thr); - class ScopedInterceptor { public: - ScopedInterceptor(ThreadState *thr, const char *fname, uptr pc) - : thr_(thr) - , in_rtl_(thr->in_rtl) { - if (thr_->in_rtl == 0) { - Initialize(thr); - FuncEntry(thr, pc); - thr_->in_rtl++; - DPrintf("#%d: intercept %s()\n", thr_->tid, fname); - } else { - thr_->in_rtl++; - } - } - - ~ScopedInterceptor() { - thr_->in_rtl--; - if (thr_->in_rtl == 0) { - FuncExit(thr_); - process_pending_signals(thr_); - } - CHECK_EQ(in_rtl_, thr_->in_rtl); - } - + ScopedInterceptor(ThreadState *thr, const char *fname, uptr pc); + ~ScopedInterceptor(); private: ThreadState *const thr_; const int in_rtl_; }; +ScopedInterceptor::ScopedInterceptor(ThreadState *thr, const char *fname, + uptr pc) + : thr_(thr) + , in_rtl_(thr->in_rtl) { + if (thr_->in_rtl == 0) { + Initialize(thr); + FuncEntry(thr, pc); + thr_->in_rtl++; + DPrintf("#%d: intercept %s()\n", thr_->tid, fname); + } else { + thr_->in_rtl++; + } +} + +ScopedInterceptor::~ScopedInterceptor() { + thr_->in_rtl--; + if (thr_->in_rtl == 0) { + FuncExit(thr_); + ProcessPendingSignals(thr_); + } + CHECK_EQ(in_rtl_, thr_->in_rtl); +} + #define SCOPED_INTERCEPTOR_RAW(func, ...) \ ThreadState *thr = cur_thread(); \ StatInc(thr, StatInterceptor); \ StatInc(thr, StatInt_##func); \ - ScopedInterceptor si(thr, #func, \ - (__sanitizer::uptr)__builtin_return_address(0)); \ - const uptr pc = (uptr)&func; \ + const uptr caller_pc = GET_CALLER_PC(); \ + ScopedInterceptor si(thr, #func, caller_pc); \ + const uptr pc = __sanitizer::StackTrace::GetPreviousInstructionPc( \ + __sanitizer::StackTrace::GetCurrentPc()); \ (void)pc; \ /**/ #define SCOPED_TSAN_INTERCEPTOR(func, ...) \ SCOPED_INTERCEPTOR_RAW(func, __VA_ARGS__); \ - if (thr->in_rtl > 1) \ - return REAL(func)(__VA_ARGS__); \ -/**/ - -#define SCOPED_INTERCEPTOR_LIBC(func, ...) \ - ThreadState *thr = cur_thread(); \ - StatInc(thr, StatInterceptor); \ - StatInc(thr, StatInt_##func); \ - ScopedInterceptor si(thr, #func, callpc); \ - const uptr pc = (uptr)&func; \ - (void)pc; \ + if (REAL(func) == 0) { \ + Printf("FATAL: ThreadSanitizer: failed to intercept %s\n", #func); \ + Die(); \ + } \ if (thr->in_rtl > 1) \ return REAL(func)(__VA_ARGS__); \ /**/ @@ -187,30 +195,40 @@ class ScopedInterceptor { #define TSAN_INTERCEPTOR(ret, func, ...) INTERCEPTOR(ret, func, __VA_ARGS__) #define TSAN_INTERCEPT(func) INTERCEPT_FUNCTION(func) -// May be overriden by front-end. -extern "C" void WEAK __tsan_malloc_hook(void *ptr, uptr size) { - (void)ptr; - (void)size; -} +#define BLOCK_REAL(name) (BlockingCall(thr), REAL(name)) -extern "C" void WEAK __tsan_free_hook(void *ptr) { - (void)ptr; +struct BlockingCall { + explicit BlockingCall(ThreadState *thr) + : ctx(SigCtx(thr)) { + ctx->in_blocking_func++; + } + + ~BlockingCall() { + ctx->in_blocking_func--; + } + + SignalContext *ctx; +}; + +TSAN_INTERCEPTOR(unsigned, sleep, unsigned sec) { + SCOPED_TSAN_INTERCEPTOR(sleep, sec); + unsigned res = BLOCK_REAL(sleep)(sec); + AfterSleep(thr, pc); + return res; } -static void invoke_malloc_hook(void *ptr, uptr size) { - Context *ctx = CTX(); - ThreadState *thr = cur_thread(); - if (ctx == 0 || !ctx->initialized || thr->in_rtl) - return; - __tsan_malloc_hook(ptr, size); +TSAN_INTERCEPTOR(int, usleep, long_t usec) { + SCOPED_TSAN_INTERCEPTOR(usleep, usec); + int res = BLOCK_REAL(usleep)(usec); + AfterSleep(thr, pc); + return res; } -static void invoke_free_hook(void *ptr) { - Context *ctx = CTX(); - ThreadState *thr = cur_thread(); - if (ctx == 0 || !ctx->initialized || thr->in_rtl) - return; - __tsan_free_hook(ptr); +TSAN_INTERCEPTOR(int, nanosleep, void *req, void *rem) { + SCOPED_TSAN_INTERCEPTOR(nanosleep, req, rem); + int res = BLOCK_REAL(nanosleep)(req, rem); + AfterSleep(thr, pc); + return res; } class AtExitContext { @@ -269,7 +287,6 @@ static void finalize(void *arg) { { ScopedInRtl in_rtl; DestroyAndFree(atexit_ctx); - usleep(flags()->atexit_sleep_ms * 1000); } int status = Finalize(cur_thread()); if (status) @@ -279,45 +296,20 @@ static void finalize(void *arg) { TSAN_INTERCEPTOR(int, atexit, void (*f)()) { SCOPED_TSAN_INTERCEPTOR(atexit, f); return atexit_ctx->atexit(thr, pc, f); - return 0; } TSAN_INTERCEPTOR(void, longjmp, void *env, int val) { SCOPED_TSAN_INTERCEPTOR(longjmp, env, val); - TsanPrintf("ThreadSanitizer: longjmp() is not supported\n"); + Printf("ThreadSanitizer: longjmp() is not supported\n"); Die(); } TSAN_INTERCEPTOR(void, siglongjmp, void *env, int val) { SCOPED_TSAN_INTERCEPTOR(siglongjmp, env, val); - TsanPrintf("ThreadSanitizer: siglongjmp() is not supported\n"); + Printf("ThreadSanitizer: siglongjmp() is not supported\n"); Die(); } -static uptr fd2addr(int fd) { - (void)fd; - static u64 addr; - return (uptr)&addr; -} - -static uptr epollfd2addr(int fd) { - (void)fd; - static u64 addr; - return (uptr)&addr; -} - -static uptr file2addr(char *path) { - (void)path; - static u64 addr; - return (uptr)&addr; -} - -static uptr dir2addr(char *path) { - (void)path; - static u64 addr; - return (uptr)&addr; -} - TSAN_INTERCEPTOR(void*, malloc, uptr size) { void *p = 0; { @@ -328,12 +320,17 @@ TSAN_INTERCEPTOR(void*, malloc, uptr size) { return p; } +TSAN_INTERCEPTOR(void*, __libc_memalign, uptr align, uptr sz) { + SCOPED_TSAN_INTERCEPTOR(__libc_memalign, align, sz); + return user_alloc(thr, pc, sz, align); +} + TSAN_INTERCEPTOR(void*, calloc, uptr size, uptr n) { void *p = 0; { SCOPED_INTERCEPTOR_RAW(calloc, size, n); p = user_alloc(thr, pc, n * size); - internal_memset(p, 0, n * size); + if (p) internal_memset(p, 0, n * size); } invoke_malloc_hook(p, n * size); return p; @@ -366,6 +363,47 @@ TSAN_INTERCEPTOR(void, cfree, void *p) { user_free(thr, pc, p); } +#define OPERATOR_NEW_BODY(mangled_name) \ + void *p = 0; \ + { \ + SCOPED_INTERCEPTOR_RAW(mangled_name, size); \ + p = user_alloc(thr, pc, size); \ + } \ + invoke_malloc_hook(p, size); \ + return p; + +void *operator new(__sanitizer::uptr size) { + OPERATOR_NEW_BODY(_Znwm); +} +void *operator new[](__sanitizer::uptr size) { + OPERATOR_NEW_BODY(_Znam); +} +void *operator new(__sanitizer::uptr size, std::nothrow_t const&) { + OPERATOR_NEW_BODY(_ZnwmRKSt9nothrow_t); +} +void *operator new[](__sanitizer::uptr size, std::nothrow_t const&) { + OPERATOR_NEW_BODY(_ZnamRKSt9nothrow_t); +} + +#define OPERATOR_DELETE_BODY(mangled_name) \ + if (ptr == 0) return; \ + invoke_free_hook(ptr); \ + SCOPED_INTERCEPTOR_RAW(mangled_name, ptr); \ + user_free(thr, pc, ptr); + +void operator delete(void *ptr) { + OPERATOR_DELETE_BODY(_ZdlPv); +} +void operator delete[](void *ptr) { + OPERATOR_DELETE_BODY(_ZdlPvRKSt9nothrow_t); +} +void operator delete(void *ptr, std::nothrow_t const&) { + OPERATOR_DELETE_BODY(_ZdaPv); +} +void operator delete[](void *ptr, std::nothrow_t const&) { + OPERATOR_DELETE_BODY(_ZdaPvRKSt9nothrow_t); +} + TSAN_INTERCEPTOR(uptr, strlen, const char *s) { SCOPED_TSAN_INTERCEPTOR(strlen, s); uptr len = internal_strlen(s); @@ -536,137 +574,61 @@ TSAN_INTERCEPTOR(int, munmap, void *addr, long_t sz) { return res; } -#ifdef __LP64__ - -// void *operator new(size_t) -TSAN_INTERCEPTOR(void*, _Znwm, uptr sz) { - void *p = 0; - { - SCOPED_TSAN_INTERCEPTOR(_Znwm, sz); - p = user_alloc(thr, pc, sz); - } - invoke_malloc_hook(p, sz); - return p; -} - -// void *operator new(size_t, nothrow_t) -TSAN_INTERCEPTOR(void*, _ZnwmRKSt9nothrow_t, uptr sz) { - void *p = 0; - { - SCOPED_TSAN_INTERCEPTOR(_ZnwmRKSt9nothrow_t, sz); - p = user_alloc(thr, pc, sz); - } - invoke_malloc_hook(p, sz); - return p; -} - -// void *operator new[](size_t) -TSAN_INTERCEPTOR(void*, _Znam, uptr sz) { - void *p = 0; - { - SCOPED_TSAN_INTERCEPTOR(_Znam, sz); - p = user_alloc(thr, pc, sz); - } - invoke_malloc_hook(p, sz); - return p; -} - -// void *operator new[](size_t, nothrow_t) -TSAN_INTERCEPTOR(void*, _ZnamRKSt9nothrow_t, uptr sz) { - void *p = 0; - { - SCOPED_TSAN_INTERCEPTOR(_ZnamRKSt9nothrow_t, sz); - p = user_alloc(thr, pc, sz); - } - invoke_malloc_hook(p, sz); - return p; -} - -#else -#error "Not implemented" -#endif - -// void operator delete(void*) -TSAN_INTERCEPTOR(void, _ZdlPv, void *p) { - if (p == 0) - return; - invoke_free_hook(p); - SCOPED_TSAN_INTERCEPTOR(_ZdlPv, p); - user_free(thr, pc, p); -} - -// void operator delete(void*, nothrow_t) -TSAN_INTERCEPTOR(void, _ZdlPvRKSt9nothrow_t, void *p) { - if (p == 0) - return; - invoke_free_hook(p); - SCOPED_TSAN_INTERCEPTOR(_ZdlPvRKSt9nothrow_t, p); - user_free(thr, pc, p); -} - -// void operator delete[](void*) -TSAN_INTERCEPTOR(void, _ZdaPv, void *p) { - if (p == 0) - return; - invoke_free_hook(p); - SCOPED_TSAN_INTERCEPTOR(_ZdaPv, p); - user_free(thr, pc, p); -} - -// void operator delete[](void*, nothrow_t) -TSAN_INTERCEPTOR(void, _ZdaPvRKSt9nothrow_t, void *p) { - if (p == 0) - return; - invoke_free_hook(p); - SCOPED_TSAN_INTERCEPTOR(_ZdaPvRKSt9nothrow_t, p); - user_free(thr, pc, p); -} - TSAN_INTERCEPTOR(void*, memalign, uptr align, uptr sz) { SCOPED_TSAN_INTERCEPTOR(memalign, align, sz); - return user_alloc_aligned(thr, pc, sz, align); + return user_alloc(thr, pc, sz, align); } TSAN_INTERCEPTOR(void*, valloc, uptr sz) { SCOPED_TSAN_INTERCEPTOR(valloc, sz); - return user_alloc_aligned(thr, pc, sz, kPageSize); + return user_alloc(thr, pc, sz, GetPageSizeCached()); } TSAN_INTERCEPTOR(void*, pvalloc, uptr sz) { SCOPED_TSAN_INTERCEPTOR(pvalloc, sz); - sz = RoundUp(sz, kPageSize); - return user_alloc_aligned(thr, pc, sz, kPageSize); + sz = RoundUp(sz, GetPageSizeCached()); + return user_alloc(thr, pc, sz, GetPageSizeCached()); } TSAN_INTERCEPTOR(int, posix_memalign, void **memptr, uptr align, uptr sz) { SCOPED_TSAN_INTERCEPTOR(posix_memalign, memptr, align, sz); - *memptr = user_alloc_aligned(thr, pc, sz, align); + *memptr = user_alloc(thr, pc, sz, align); return 0; } // Used in thread-safe function static initialization. -TSAN_INTERCEPTOR(int, __cxa_guard_acquire, char *m) { - SCOPED_TSAN_INTERCEPTOR(__cxa_guard_acquire, m); - int res = REAL(__cxa_guard_acquire)(m); - if (res) { - // This thread does the init. - } else { - Acquire(thr, pc, (uptr)m); +extern "C" int INTERFACE_ATTRIBUTE __cxa_guard_acquire(atomic_uint32_t *g) { + SCOPED_INTERCEPTOR_RAW(__cxa_guard_acquire, g); + for (;;) { + u32 cmp = atomic_load(g, memory_order_acquire); + if (cmp == 0) { + if (atomic_compare_exchange_strong(g, &cmp, 1<<16, memory_order_relaxed)) + return 1; + } else if (cmp == 1) { + Acquire(thr, pc, (uptr)g); + return 0; + } else { + internal_sched_yield(); + } } - return res; } -TSAN_INTERCEPTOR(void, __cxa_guard_release, char *m) { - SCOPED_TSAN_INTERCEPTOR(__cxa_guard_release, m); - Release(thr, pc, (uptr)m); - REAL(__cxa_guard_release)(m); +extern "C" void INTERFACE_ATTRIBUTE __cxa_guard_release(atomic_uint32_t *g) { + SCOPED_INTERCEPTOR_RAW(__cxa_guard_release, g); + Release(thr, pc, (uptr)g); + atomic_store(g, 1, memory_order_release); +} + +extern "C" void INTERFACE_ATTRIBUTE __cxa_guard_abort(atomic_uint32_t *g) { + SCOPED_INTERCEPTOR_RAW(__cxa_guard_abort, g); + atomic_store(g, 0, memory_order_relaxed); } static void thread_finalize(void *v) { uptr iter = (uptr)v; if (iter > 1) { if (pthread_setspecific(g_thread_finalize_key, (void*)(iter - 1))) { - TsanPrintf("ThreadSanitizer: failed to set thread key\n"); + Printf("ThreadSanitizer: failed to set thread key\n"); Die(); } return; @@ -678,7 +640,7 @@ static void thread_finalize(void *v) { SignalContext *sctx = thr->signal_ctx; if (sctx) { thr->signal_ctx = 0; - internal_free(sctx); + UnmapOrDie(sctx, sizeof(*sctx)); } } } @@ -699,13 +661,13 @@ extern "C" void *__tsan_thread_start_func(void *arg) { ThreadState *thr = cur_thread(); ScopedInRtl in_rtl; if (pthread_setspecific(g_thread_finalize_key, (void*)4)) { - TsanPrintf("ThreadSanitizer: failed to set thread key\n"); + Printf("ThreadSanitizer: failed to set thread key\n"); Die(); } while ((tid = atomic_load(&p->tid, memory_order_acquire)) == 0) pthread_yield(); atomic_store(&p->tid, 0, memory_order_release); - ThreadStart(thr, tid); + ThreadStart(thr, tid, GetTid()); CHECK_EQ(thr->in_rtl, 1); } void *res = callback(param); @@ -740,7 +702,7 @@ TSAN_INTERCEPTOR(int, pthread_create, atomic_store(&p.tid, 0, memory_order_relaxed); int res = REAL(pthread_create)(th, attr, __tsan_thread_start_func, &p); if (res == 0) { - int tid = ThreadCreate(cur_thread(), pc, *(uptr*)th, detached); + int tid = ThreadCreate(thr, pc, *(uptr*)th, detached); CHECK_NE(tid, 0); atomic_store(&p.tid, tid, memory_order_release); while (atomic_load(&p.tid, memory_order_acquire) != 0) @@ -754,9 +716,9 @@ TSAN_INTERCEPTOR(int, pthread_create, TSAN_INTERCEPTOR(int, pthread_join, void *th, void **ret) { SCOPED_TSAN_INTERCEPTOR(pthread_join, th, ret); int tid = ThreadTid(thr, pc, (uptr)th); - int res = REAL(pthread_join)(th, ret); + int res = BLOCK_REAL(pthread_join)(th, ret); if (res == 0) { - ThreadJoin(cur_thread(), pc, tid); + ThreadJoin(thr, pc, tid); } return res; } @@ -766,7 +728,7 @@ TSAN_INTERCEPTOR(int, pthread_detach, void *th) { int tid = ThreadTid(thr, pc, (uptr)th); int res = REAL(pthread_detach)(th); if (res == 0) { - ThreadDetach(cur_thread(), pc, tid); + ThreadDetach(thr, pc, tid); } return res; } @@ -782,7 +744,7 @@ TSAN_INTERCEPTOR(int, pthread_mutex_init, void *m, void *a) { recursive = (type == PTHREAD_MUTEX_RECURSIVE || type == PTHREAD_MUTEX_RECURSIVE_NP); } - MutexCreate(cur_thread(), pc, (uptr)m, false, recursive); + MutexCreate(thr, pc, (uptr)m, false, recursive, false); } return res; } @@ -791,7 +753,7 @@ TSAN_INTERCEPTOR(int, pthread_mutex_destroy, void *m) { SCOPED_TSAN_INTERCEPTOR(pthread_mutex_destroy, m); int res = REAL(pthread_mutex_destroy)(m); if (res == 0 || res == EBUSY) { - MutexDestroy(cur_thread(), pc, (uptr)m); + MutexDestroy(thr, pc, (uptr)m); } return res; } @@ -800,7 +762,7 @@ TSAN_INTERCEPTOR(int, pthread_mutex_lock, void *m) { SCOPED_TSAN_INTERCEPTOR(pthread_mutex_lock, m); int res = REAL(pthread_mutex_lock)(m); if (res == 0) { - MutexLock(cur_thread(), pc, (uptr)m); + MutexLock(thr, pc, (uptr)m); } return res; } @@ -809,7 +771,7 @@ TSAN_INTERCEPTOR(int, pthread_mutex_trylock, void *m) { SCOPED_TSAN_INTERCEPTOR(pthread_mutex_trylock, m); int res = REAL(pthread_mutex_trylock)(m); if (res == 0) { - MutexLock(cur_thread(), pc, (uptr)m); + MutexLock(thr, pc, (uptr)m); } return res; } @@ -818,14 +780,14 @@ TSAN_INTERCEPTOR(int, pthread_mutex_timedlock, void *m, void *abstime) { SCOPED_TSAN_INTERCEPTOR(pthread_mutex_timedlock, m, abstime); int res = REAL(pthread_mutex_timedlock)(m, abstime); if (res == 0) { - MutexLock(cur_thread(), pc, (uptr)m); + MutexLock(thr, pc, (uptr)m); } return res; } TSAN_INTERCEPTOR(int, pthread_mutex_unlock, void *m) { SCOPED_TSAN_INTERCEPTOR(pthread_mutex_unlock, m); - MutexUnlock(cur_thread(), pc, (uptr)m); + MutexUnlock(thr, pc, (uptr)m); int res = REAL(pthread_mutex_unlock)(m); return res; } @@ -834,7 +796,7 @@ TSAN_INTERCEPTOR(int, pthread_spin_init, void *m, int pshared) { SCOPED_TSAN_INTERCEPTOR(pthread_spin_init, m, pshared); int res = REAL(pthread_spin_init)(m, pshared); if (res == 0) { - MutexCreate(cur_thread(), pc, (uptr)m, false, false); + MutexCreate(thr, pc, (uptr)m, false, false, false); } return res; } @@ -843,7 +805,7 @@ TSAN_INTERCEPTOR(int, pthread_spin_destroy, void *m) { SCOPED_TSAN_INTERCEPTOR(pthread_spin_destroy, m); int res = REAL(pthread_spin_destroy)(m); if (res == 0) { - MutexDestroy(cur_thread(), pc, (uptr)m); + MutexDestroy(thr, pc, (uptr)m); } return res; } @@ -852,7 +814,7 @@ TSAN_INTERCEPTOR(int, pthread_spin_lock, void *m) { SCOPED_TSAN_INTERCEPTOR(pthread_spin_lock, m); int res = REAL(pthread_spin_lock)(m); if (res == 0) { - MutexLock(cur_thread(), pc, (uptr)m); + MutexLock(thr, pc, (uptr)m); } return res; } @@ -861,14 +823,14 @@ TSAN_INTERCEPTOR(int, pthread_spin_trylock, void *m) { SCOPED_TSAN_INTERCEPTOR(pthread_spin_trylock, m); int res = REAL(pthread_spin_trylock)(m); if (res == 0) { - MutexLock(cur_thread(), pc, (uptr)m); + MutexLock(thr, pc, (uptr)m); } return res; } TSAN_INTERCEPTOR(int, pthread_spin_unlock, void *m) { SCOPED_TSAN_INTERCEPTOR(pthread_spin_unlock, m); - MutexUnlock(cur_thread(), pc, (uptr)m); + MutexUnlock(thr, pc, (uptr)m); int res = REAL(pthread_spin_unlock)(m); return res; } @@ -877,7 +839,7 @@ TSAN_INTERCEPTOR(int, pthread_rwlock_init, void *m, void *a) { SCOPED_TSAN_INTERCEPTOR(pthread_rwlock_init, m, a); int res = REAL(pthread_rwlock_init)(m, a); if (res == 0) { - MutexCreate(cur_thread(), pc, (uptr)m, true, false); + MutexCreate(thr, pc, (uptr)m, true, false, false); } return res; } @@ -886,7 +848,7 @@ TSAN_INTERCEPTOR(int, pthread_rwlock_destroy, void *m) { SCOPED_TSAN_INTERCEPTOR(pthread_rwlock_destroy, m); int res = REAL(pthread_rwlock_destroy)(m); if (res == 0) { - MutexDestroy(cur_thread(), pc, (uptr)m); + MutexDestroy(thr, pc, (uptr)m); } return res; } @@ -895,7 +857,7 @@ TSAN_INTERCEPTOR(int, pthread_rwlock_rdlock, void *m) { SCOPED_TSAN_INTERCEPTOR(pthread_rwlock_rdlock, m); int res = REAL(pthread_rwlock_rdlock)(m); if (res == 0) { - MutexReadLock(cur_thread(), pc, (uptr)m); + MutexReadLock(thr, pc, (uptr)m); } return res; } @@ -904,7 +866,7 @@ TSAN_INTERCEPTOR(int, pthread_rwlock_tryrdlock, void *m) { SCOPED_TSAN_INTERCEPTOR(pthread_rwlock_tryrdlock, m); int res = REAL(pthread_rwlock_tryrdlock)(m); if (res == 0) { - MutexReadLock(cur_thread(), pc, (uptr)m); + MutexReadLock(thr, pc, (uptr)m); } return res; } @@ -913,7 +875,7 @@ TSAN_INTERCEPTOR(int, pthread_rwlock_timedrdlock, void *m, void *abstime) { SCOPED_TSAN_INTERCEPTOR(pthread_rwlock_timedrdlock, m, abstime); int res = REAL(pthread_rwlock_timedrdlock)(m, abstime); if (res == 0) { - MutexReadLock(cur_thread(), pc, (uptr)m); + MutexReadLock(thr, pc, (uptr)m); } return res; } @@ -922,7 +884,7 @@ TSAN_INTERCEPTOR(int, pthread_rwlock_wrlock, void *m) { SCOPED_TSAN_INTERCEPTOR(pthread_rwlock_wrlock, m); int res = REAL(pthread_rwlock_wrlock)(m); if (res == 0) { - MutexLock(cur_thread(), pc, (uptr)m); + MutexLock(thr, pc, (uptr)m); } return res; } @@ -931,7 +893,7 @@ TSAN_INTERCEPTOR(int, pthread_rwlock_trywrlock, void *m) { SCOPED_TSAN_INTERCEPTOR(pthread_rwlock_trywrlock, m); int res = REAL(pthread_rwlock_trywrlock)(m); if (res == 0) { - MutexLock(cur_thread(), pc, (uptr)m); + MutexLock(thr, pc, (uptr)m); } return res; } @@ -940,23 +902,27 @@ TSAN_INTERCEPTOR(int, pthread_rwlock_timedwrlock, void *m, void *abstime) { SCOPED_TSAN_INTERCEPTOR(pthread_rwlock_timedwrlock, m, abstime); int res = REAL(pthread_rwlock_timedwrlock)(m, abstime); if (res == 0) { - MutexLock(cur_thread(), pc, (uptr)m); + MutexLock(thr, pc, (uptr)m); } return res; } TSAN_INTERCEPTOR(int, pthread_rwlock_unlock, void *m) { SCOPED_TSAN_INTERCEPTOR(pthread_rwlock_unlock, m); - MutexReadOrWriteUnlock(cur_thread(), pc, (uptr)m); + MutexReadOrWriteUnlock(thr, pc, (uptr)m); int res = REAL(pthread_rwlock_unlock)(m); return res; } +// libpthread.so contains several versions of pthread_cond_init symbol. +// When we just dlsym() it, we get the wrong (old) version. +/* TSAN_INTERCEPTOR(int, pthread_cond_init, void *c, void *a) { SCOPED_TSAN_INTERCEPTOR(pthread_cond_init, c, a); int res = REAL(pthread_cond_init)(c, a); return res; } +*/ TSAN_INTERCEPTOR(int, pthread_cond_destroy, void *c) { SCOPED_TSAN_INTERCEPTOR(pthread_cond_destroy, c); @@ -978,17 +944,17 @@ TSAN_INTERCEPTOR(int, pthread_cond_broadcast, void *c) { TSAN_INTERCEPTOR(int, pthread_cond_wait, void *c, void *m) { SCOPED_TSAN_INTERCEPTOR(pthread_cond_wait, c, m); - MutexUnlock(cur_thread(), pc, (uptr)m); + MutexUnlock(thr, pc, (uptr)m); int res = REAL(pthread_cond_wait)(c, m); - MutexLock(cur_thread(), pc, (uptr)m); + MutexLock(thr, pc, (uptr)m); return res; } TSAN_INTERCEPTOR(int, pthread_cond_timedwait, void *c, void *m, void *abstime) { SCOPED_TSAN_INTERCEPTOR(pthread_cond_timedwait, c, m, abstime); - MutexUnlock(cur_thread(), pc, (uptr)m); + MutexUnlock(thr, pc, (uptr)m); int res = REAL(pthread_cond_timedwait)(c, m, abstime); - MutexLock(cur_thread(), pc, (uptr)m); + MutexLock(thr, pc, (uptr)m); return res; } @@ -1008,12 +974,12 @@ TSAN_INTERCEPTOR(int, pthread_barrier_destroy, void *b) { TSAN_INTERCEPTOR(int, pthread_barrier_wait, void *b) { SCOPED_TSAN_INTERCEPTOR(pthread_barrier_wait, b); - Release(cur_thread(), pc, (uptr)b); + Release(thr, pc, (uptr)b); MemoryRead1Byte(thr, pc, (uptr)b); int res = REAL(pthread_barrier_wait)(b); MemoryRead1Byte(thr, pc, (uptr)b); if (res == 0 || res == PTHREAD_BARRIER_SERIAL_THREAD) { - Acquire(cur_thread(), pc, (uptr)b); + Acquire(thr, pc, (uptr)b); } return res; } @@ -1031,14 +997,14 @@ TSAN_INTERCEPTOR(int, pthread_once, void *o, void (*f)()) { (*f)(); CHECK_EQ(thr->in_rtl, 0); thr->in_rtl = old_in_rtl; - Release(cur_thread(), pc, (uptr)o); + Release(thr, pc, (uptr)o); atomic_store(a, 2, memory_order_release); } else { while (v != 2) { pthread_yield(); v = atomic_load(a, memory_order_acquire); } - Acquire(cur_thread(), pc, (uptr)o); + Acquire(thr, pc, (uptr)o); } return 0; } @@ -1057,34 +1023,34 @@ TSAN_INTERCEPTOR(int, sem_destroy, void *s) { TSAN_INTERCEPTOR(int, sem_wait, void *s) { SCOPED_TSAN_INTERCEPTOR(sem_wait, s); - int res = REAL(sem_wait)(s); + int res = BLOCK_REAL(sem_wait)(s); if (res == 0) { - Acquire(cur_thread(), pc, (uptr)s); + Acquire(thr, pc, (uptr)s); } return res; } TSAN_INTERCEPTOR(int, sem_trywait, void *s) { SCOPED_TSAN_INTERCEPTOR(sem_trywait, s); - int res = REAL(sem_trywait)(s); + int res = BLOCK_REAL(sem_trywait)(s); if (res == 0) { - Acquire(cur_thread(), pc, (uptr)s); + Acquire(thr, pc, (uptr)s); } return res; } TSAN_INTERCEPTOR(int, sem_timedwait, void *s, void *abstime) { SCOPED_TSAN_INTERCEPTOR(sem_timedwait, s, abstime); - int res = REAL(sem_timedwait)(s, abstime); + int res = BLOCK_REAL(sem_timedwait)(s, abstime); if (res == 0) { - Acquire(cur_thread(), pc, (uptr)s); + Acquire(thr, pc, (uptr)s); } return res; } TSAN_INTERCEPTOR(int, sem_post, void *s) { SCOPED_TSAN_INTERCEPTOR(sem_post, s); - Release(cur_thread(), pc, (uptr)s); + Release(thr, pc, (uptr)s); int res = REAL(sem_post)(s); return res; } @@ -1093,43 +1059,193 @@ TSAN_INTERCEPTOR(int, sem_getvalue, void *s, int *sval) { SCOPED_TSAN_INTERCEPTOR(sem_getvalue, s, sval); int res = REAL(sem_getvalue)(s, sval); if (res == 0) { - Acquire(cur_thread(), pc, (uptr)s); + Acquire(thr, pc, (uptr)s); } return res; } -TSAN_INTERCEPTOR(long_t, read, int fd, void *buf, long_t sz) { - SCOPED_TSAN_INTERCEPTOR(read, fd, buf, sz); - int res = REAL(read)(fd, buf, sz); - if (res >= 0) { - Acquire(cur_thread(), pc, fd2addr(fd)); - } +TSAN_INTERCEPTOR(int, open, const char *name, int flags, int mode) { + SCOPED_TSAN_INTERCEPTOR(open, name, flags, mode); + int fd = REAL(open)(name, flags, mode); + if (fd >= 0) + FdFileCreate(thr, pc, fd); + return fd; +} + +TSAN_INTERCEPTOR(int, open64, const char *name, int flags, int mode) { + SCOPED_TSAN_INTERCEPTOR(open64, name, flags, mode); + int fd = REAL(open64)(name, flags, mode); + if (fd >= 0) + FdFileCreate(thr, pc, fd); + return fd; +} + +TSAN_INTERCEPTOR(int, creat, const char *name, int mode) { + SCOPED_TSAN_INTERCEPTOR(creat, name, mode); + int fd = REAL(creat)(name, mode); + if (fd >= 0) + FdFileCreate(thr, pc, fd); + return fd; +} + +TSAN_INTERCEPTOR(int, creat64, const char *name, int mode) { + SCOPED_TSAN_INTERCEPTOR(creat64, name, mode); + int fd = REAL(creat64)(name, mode); + if (fd >= 0) + FdFileCreate(thr, pc, fd); + return fd; +} + +TSAN_INTERCEPTOR(int, dup, int oldfd) { + SCOPED_TSAN_INTERCEPTOR(dup, oldfd); + int newfd = REAL(dup)(oldfd); + if (oldfd >= 0 && newfd >= 0 && newfd != oldfd) + FdDup(thr, pc, oldfd, newfd); + return newfd; +} + +TSAN_INTERCEPTOR(int, dup2, int oldfd, int newfd) { + SCOPED_TSAN_INTERCEPTOR(dup2, oldfd, newfd); + int newfd2 = REAL(dup2)(oldfd, newfd); + if (oldfd >= 0 && newfd2 >= 0 && newfd2 != oldfd) + FdDup(thr, pc, oldfd, newfd2); + return newfd2; +} + +TSAN_INTERCEPTOR(int, dup3, int oldfd, int newfd, int flags) { + SCOPED_TSAN_INTERCEPTOR(dup3, oldfd, newfd, flags); + int newfd2 = REAL(dup3)(oldfd, newfd, flags); + if (oldfd >= 0 && newfd2 >= 0 && newfd2 != oldfd) + FdDup(thr, pc, oldfd, newfd2); + return newfd2; +} + +TSAN_INTERCEPTOR(int, eventfd, unsigned initval, int flags) { + SCOPED_TSAN_INTERCEPTOR(eventfd, initval, flags); + int fd = REAL(eventfd)(initval, flags); + if (fd >= 0) + FdEventCreate(thr, pc, fd); + return fd; +} + +TSAN_INTERCEPTOR(int, signalfd, int fd, void *mask, int flags) { + SCOPED_TSAN_INTERCEPTOR(signalfd, fd, mask, flags); + if (fd >= 0) + FdClose(thr, pc, fd); + fd = REAL(signalfd)(fd, mask, flags); + if (fd >= 0) + FdSignalCreate(thr, pc, fd); + return fd; +} + +TSAN_INTERCEPTOR(int, inotify_init, int fake) { + SCOPED_TSAN_INTERCEPTOR(inotify_init, fake); + int fd = REAL(inotify_init)(fake); + if (fd >= 0) + FdInotifyCreate(thr, pc, fd); + return fd; +} + +TSAN_INTERCEPTOR(int, inotify_init1, int flags) { + SCOPED_TSAN_INTERCEPTOR(inotify_init1, flags); + int fd = REAL(inotify_init1)(flags); + if (fd >= 0) + FdInotifyCreate(thr, pc, fd); + return fd; +} + +TSAN_INTERCEPTOR(int, socket, int domain, int type, int protocol) { + SCOPED_TSAN_INTERCEPTOR(socket, domain, type, protocol); + int fd = REAL(socket)(domain, type, protocol); + if (fd >= 0) + FdSocketCreate(thr, pc, fd); + return fd; +} + +TSAN_INTERCEPTOR(int, socketpair, int domain, int type, int protocol, int *fd) { + SCOPED_TSAN_INTERCEPTOR(socketpair, domain, type, protocol, fd); + int res = REAL(socketpair)(domain, type, protocol, fd); + if (res == 0 && fd[0] >= 0 && fd[1] >= 0) + FdPipeCreate(thr, pc, fd[0], fd[1]); return res; } -TSAN_INTERCEPTOR(long_t, pread, int fd, void *buf, long_t sz, unsigned off) { - SCOPED_TSAN_INTERCEPTOR(pread, fd, buf, sz, off); - int res = REAL(pread)(fd, buf, sz, off); - if (res >= 0) { - Acquire(cur_thread(), pc, fd2addr(fd)); - } +TSAN_INTERCEPTOR(int, connect, int fd, void *addr, unsigned addrlen) { + SCOPED_TSAN_INTERCEPTOR(connect, fd, addr, addrlen); + FdSocketConnecting(thr, pc, fd); + int res = REAL(connect)(fd, addr, addrlen); + if (res == 0 && fd >= 0) + FdSocketConnect(thr, pc, fd); return res; } -TSAN_INTERCEPTOR(long_t, pread64, int fd, void *buf, long_t sz, u64 off) { - SCOPED_TSAN_INTERCEPTOR(pread64, fd, buf, sz, off); - int res = REAL(pread64)(fd, buf, sz, off); - if (res >= 0) { - Acquire(cur_thread(), pc, fd2addr(fd)); - } +TSAN_INTERCEPTOR(int, accept, int fd, void *addr, unsigned *addrlen) { + SCOPED_TSAN_INTERCEPTOR(accept, fd, addr, addrlen); + int fd2 = REAL(accept)(fd, addr, addrlen); + if (fd >= 0 && fd2 >= 0) + FdSocketAccept(thr, pc, fd, fd2); + return fd2; +} + +TSAN_INTERCEPTOR(int, accept4, int fd, void *addr, unsigned *addrlen, int f) { + SCOPED_TSAN_INTERCEPTOR(accept4, fd, addr, addrlen, f); + int fd2 = REAL(accept4)(fd, addr, addrlen, f); + if (fd >= 0 && fd2 >= 0) + FdSocketAccept(thr, pc, fd, fd2); + return fd2; +} + +TSAN_INTERCEPTOR(int, epoll_create, int size) { + SCOPED_TSAN_INTERCEPTOR(epoll_create, size); + int fd = REAL(epoll_create)(size); + if (fd >= 0) + FdPollCreate(thr, pc, fd); + return fd; +} + +TSAN_INTERCEPTOR(int, epoll_create1, int flags) { + SCOPED_TSAN_INTERCEPTOR(epoll_create1, flags); + int fd = REAL(epoll_create1)(flags); + if (fd >= 0) + FdPollCreate(thr, pc, fd); + return fd; +} + +TSAN_INTERCEPTOR(int, close, int fd) { + SCOPED_TSAN_INTERCEPTOR(close, fd); + if (fd >= 0) + FdClose(thr, pc, fd); + return REAL(close)(fd); +} + +TSAN_INTERCEPTOR(int, __close, int fd) { + SCOPED_TSAN_INTERCEPTOR(__close, fd); + if (fd >= 0) + FdClose(thr, pc, fd); + return REAL(__close)(fd); +} + +TSAN_INTERCEPTOR(int, pipe, int *pipefd) { + SCOPED_TSAN_INTERCEPTOR(pipe, pipefd); + int res = REAL(pipe)(pipefd); + if (res == 0 && pipefd[0] >= 0 && pipefd[1] >= 0) + FdPipeCreate(thr, pc, pipefd[0], pipefd[1]); + return res; +} + +TSAN_INTERCEPTOR(int, pipe2, int *pipefd, int flags) { + SCOPED_TSAN_INTERCEPTOR(pipe2, pipefd, flags); + int res = REAL(pipe2)(pipefd, flags); + if (res == 0 && pipefd[0] >= 0 && pipefd[1] >= 0) + FdPipeCreate(thr, pc, pipefd[0], pipefd[1]); return res; } TSAN_INTERCEPTOR(long_t, readv, int fd, void *vec, int cnt) { SCOPED_TSAN_INTERCEPTOR(readv, fd, vec, cnt); int res = REAL(readv)(fd, vec, cnt); - if (res >= 0) { - Acquire(cur_thread(), pc, fd2addr(fd)); + if (res >= 0 && fd >= 0) { + FdAcquire(thr, pc, fd); } return res; } @@ -1137,57 +1253,40 @@ TSAN_INTERCEPTOR(long_t, readv, int fd, void *vec, int cnt) { TSAN_INTERCEPTOR(long_t, preadv64, int fd, void *vec, int cnt, u64 off) { SCOPED_TSAN_INTERCEPTOR(preadv64, fd, vec, cnt, off); int res = REAL(preadv64)(fd, vec, cnt, off); - if (res >= 0) { - Acquire(cur_thread(), pc, fd2addr(fd)); + if (res >= 0 && fd >= 0) { + FdAcquire(thr, pc, fd); } return res; } -TSAN_INTERCEPTOR(long_t, write, int fd, void *buf, long_t sz) { - SCOPED_TSAN_INTERCEPTOR(write, fd, buf, sz); - Release(cur_thread(), pc, fd2addr(fd)); - int res = REAL(write)(fd, buf, sz); - return res; -} - -TSAN_INTERCEPTOR(long_t, pwrite, int fd, void *buf, long_t sz, unsigned off) { - SCOPED_TSAN_INTERCEPTOR(pwrite, fd, buf, sz, off); - Release(cur_thread(), pc, fd2addr(fd)); - int res = REAL(pwrite)(fd, buf, sz, off); - return res; -} - -TSAN_INTERCEPTOR(long_t, pwrite64, int fd, void *buf, long_t sz, unsigned off) { - SCOPED_TSAN_INTERCEPTOR(pwrite64, fd, buf, sz, off); - Release(cur_thread(), pc, fd2addr(fd)); - int res = REAL(pwrite64)(fd, buf, sz, off); - return res; -} - TSAN_INTERCEPTOR(long_t, writev, int fd, void *vec, int cnt) { SCOPED_TSAN_INTERCEPTOR(writev, fd, vec, cnt); - Release(cur_thread(), pc, fd2addr(fd)); + if (fd >= 0) + FdRelease(thr, pc, fd); int res = REAL(writev)(fd, vec, cnt); return res; } TSAN_INTERCEPTOR(long_t, pwritev64, int fd, void *vec, int cnt, u64 off) { SCOPED_TSAN_INTERCEPTOR(pwritev64, fd, vec, cnt, off); - Release(cur_thread(), pc, fd2addr(fd)); + if (fd >= 0) + FdRelease(thr, pc, fd); int res = REAL(pwritev64)(fd, vec, cnt, off); return res; } TSAN_INTERCEPTOR(long_t, send, int fd, void *buf, long_t len, int flags) { SCOPED_TSAN_INTERCEPTOR(send, fd, buf, len, flags); - Release(cur_thread(), pc, fd2addr(fd)); + if (fd >= 0) + FdRelease(thr, pc, fd); int res = REAL(send)(fd, buf, len, flags); return res; } TSAN_INTERCEPTOR(long_t, sendmsg, int fd, void *msg, int flags) { SCOPED_TSAN_INTERCEPTOR(sendmsg, fd, msg, flags); - Release(cur_thread(), pc, fd2addr(fd)); + if (fd >= 0) + FdRelease(thr, pc, fd); int res = REAL(sendmsg)(fd, msg, flags); return res; } @@ -1195,8 +1294,8 @@ TSAN_INTERCEPTOR(long_t, sendmsg, int fd, void *msg, int flags) { TSAN_INTERCEPTOR(long_t, recv, int fd, void *buf, long_t len, int flags) { SCOPED_TSAN_INTERCEPTOR(recv, fd, buf, len, flags); int res = REAL(recv)(fd, buf, len, flags); - if (res >= 0) { - Acquire(cur_thread(), pc, fd2addr(fd)); + if (res >= 0 && fd >= 0) { + FdAcquire(thr, pc, fd); } return res; } @@ -1204,15 +1303,15 @@ TSAN_INTERCEPTOR(long_t, recv, int fd, void *buf, long_t len, int flags) { TSAN_INTERCEPTOR(long_t, recvmsg, int fd, void *msg, int flags) { SCOPED_TSAN_INTERCEPTOR(recvmsg, fd, msg, flags); int res = REAL(recvmsg)(fd, msg, flags); - if (res >= 0) { - Acquire(cur_thread(), pc, fd2addr(fd)); + if (res >= 0 && fd >= 0) { + FdAcquire(thr, pc, fd); } return res; } TSAN_INTERCEPTOR(int, unlink, char *path) { SCOPED_TSAN_INTERCEPTOR(unlink, path); - Release(cur_thread(), pc, file2addr(path)); + Release(thr, pc, File2addr(path)); int res = REAL(unlink)(path); return res; } @@ -1220,19 +1319,57 @@ TSAN_INTERCEPTOR(int, unlink, char *path) { TSAN_INTERCEPTOR(void*, fopen, char *path, char *mode) { SCOPED_TSAN_INTERCEPTOR(fopen, path, mode); void *res = REAL(fopen)(path, mode); - Acquire(cur_thread(), pc, file2addr(path)); + Acquire(thr, pc, File2addr(path)); + if (res) { + int fd = fileno_unlocked(res); + if (fd >= 0) + FdFileCreate(thr, pc, fd); + } return res; } +TSAN_INTERCEPTOR(void*, freopen, char *path, char *mode, void *stream) { + SCOPED_TSAN_INTERCEPTOR(freopen, path, mode, stream); + if (stream) { + int fd = fileno_unlocked(stream); + if (fd >= 0) + FdClose(thr, pc, fd); + } + void *res = REAL(freopen)(path, mode, stream); + Acquire(thr, pc, File2addr(path)); + if (res) { + int fd = fileno_unlocked(res); + if (fd >= 0) + FdFileCreate(thr, pc, fd); + } + return res; +} + +TSAN_INTERCEPTOR(int, fclose, void *stream) { + { + SCOPED_TSAN_INTERCEPTOR(fclose, stream); + if (stream) { + int fd = fileno_unlocked(stream); + if (fd >= 0) + FdClose(thr, pc, fd); + } + } + return REAL(fclose)(stream); +} + TSAN_INTERCEPTOR(uptr, fread, void *ptr, uptr size, uptr nmemb, void *f) { - SCOPED_TSAN_INTERCEPTOR(fread, ptr, size, nmemb, f); - MemoryAccessRange(thr, pc, (uptr)ptr, size * nmemb, true); + { + SCOPED_TSAN_INTERCEPTOR(fread, ptr, size, nmemb, f); + MemoryAccessRange(thr, pc, (uptr)ptr, size * nmemb, true); + } return REAL(fread)(ptr, size, nmemb, f); } TSAN_INTERCEPTOR(uptr, fwrite, const void *p, uptr size, uptr nmemb, void *f) { - SCOPED_TSAN_INTERCEPTOR(fwrite, p, size, nmemb, f); - MemoryAccessRange(thr, pc, (uptr)p, size * nmemb, false); + { + SCOPED_TSAN_INTERCEPTOR(fwrite, p, size, nmemb, f); + MemoryAccessRange(thr, pc, (uptr)p, size * nmemb, false); + } return REAL(fwrite)(p, size, nmemb, f); } @@ -1244,7 +1381,7 @@ TSAN_INTERCEPTOR(int, puts, const char *s) { TSAN_INTERCEPTOR(int, rmdir, char *path) { SCOPED_TSAN_INTERCEPTOR(rmdir, path); - Release(cur_thread(), pc, dir2addr(path)); + Release(thr, pc, Dir2addr(path)); int res = REAL(rmdir)(path); return res; } @@ -1252,28 +1389,37 @@ TSAN_INTERCEPTOR(int, rmdir, char *path) { TSAN_INTERCEPTOR(void*, opendir, char *path) { SCOPED_TSAN_INTERCEPTOR(opendir, path); void *res = REAL(opendir)(path); - Acquire(cur_thread(), pc, dir2addr(path)); + if (res != 0) + Acquire(thr, pc, Dir2addr(path)); return res; } TSAN_INTERCEPTOR(int, epoll_ctl, int epfd, int op, int fd, void *ev) { SCOPED_TSAN_INTERCEPTOR(epoll_ctl, epfd, op, fd, ev); - if (op == EPOLL_CTL_ADD) { - Release(cur_thread(), pc, epollfd2addr(epfd)); + if (op == EPOLL_CTL_ADD && epfd >= 0) { + FdRelease(thr, pc, epfd); } int res = REAL(epoll_ctl)(epfd, op, fd, ev); + if (fd >= 0) + FdAccess(thr, pc, fd); return res; } TSAN_INTERCEPTOR(int, epoll_wait, int epfd, void *ev, int cnt, int timeout) { SCOPED_TSAN_INTERCEPTOR(epoll_wait, epfd, ev, cnt, timeout); - int res = REAL(epoll_wait)(epfd, ev, cnt, timeout); - if (res > 0) { - Acquire(cur_thread(), pc, epollfd2addr(epfd)); + int res = BLOCK_REAL(epoll_wait)(epfd, ev, cnt, timeout); + if (res > 0 && epfd >= 0) { + FdAcquire(thr, pc, epfd); } return res; } +TSAN_INTERCEPTOR(int, poll, void *fds, long_t nfds, int timeout) { + SCOPED_TSAN_INTERCEPTOR(poll, fds, nfds, timeout); + int res = BLOCK_REAL(poll)(fds, nfds, timeout); + return res; +} + static void ALWAYS_INLINE rtl_generic_sighandler(bool sigact, int sig, my_siginfo_t *info, void *ctx) { ThreadState *thr = cur_thread(); @@ -1281,7 +1427,12 @@ static void ALWAYS_INLINE rtl_generic_sighandler(bool sigact, int sig, // Don't mess with synchronous signals. if (sig == SIGSEGV || sig == SIGBUS || sig == SIGILL || sig == SIGABRT || sig == SIGFPE || sig == SIGPIPE || - (sctx && sig == sctx->int_signal_send)) { + // If we are sending signal to ourselves, we must process it now. + (sctx && sig == sctx->int_signal_send) || + // If we are in blocking function, we can safely process it now + // (but check if we are in a recursive interceptor, + // i.e. pthread_join()->munmap()). + (sctx && sctx->in_blocking_func == 1 && thr->in_rtl == 1)) { CHECK(thr->in_rtl == 0 || thr->in_rtl == 1); int in_rtl = thr->in_rtl; thr->in_rtl = 0; @@ -1340,11 +1491,11 @@ TSAN_INTERCEPTOR(int, sigaction, int sig, sigaction_t *act, sigaction_t *old) { } TSAN_INTERCEPTOR(sighandler_t, signal, int sig, sighandler_t h) { - sigaction_t act = {}; + sigaction_t act; act.sa_handler = h; REAL(memset)(&act.sa_mask, -1, sizeof(act.sa_mask)); act.sa_flags = 0; - sigaction_t old = {}; + sigaction_t old; int res = sigaction(sig, &act, &old); if (res) return SIG_ERR; @@ -1395,11 +1546,89 @@ TSAN_INTERCEPTOR(int, pthread_kill, void *tid, int sig) { return res; } -static void process_pending_signals(ThreadState *thr) { +TSAN_INTERCEPTOR(int, gettimeofday, void *tv, void *tz) { + SCOPED_TSAN_INTERCEPTOR(gettimeofday, tv, tz); + // It's intercepted merely to process pending signals. + return REAL(gettimeofday)(tv, tz); +} + +// Linux kernel has a bug that leads to kernel deadlock if a process +// maps TBs of memory and then calls mlock(). +static void MlockIsUnsupported() { + static atomic_uint8_t printed; + if (atomic_exchange(&printed, 1, memory_order_relaxed)) + return; + Printf("INFO: ThreadSanitizer ignores mlock/mlockall/munlock/munlockall\n"); +} + +TSAN_INTERCEPTOR(int, mlock, const void *addr, uptr len) { + MlockIsUnsupported(); + return 0; +} + +TSAN_INTERCEPTOR(int, munlock, const void *addr, uptr len) { + MlockIsUnsupported(); + return 0; +} + +TSAN_INTERCEPTOR(int, mlockall, int flags) { + MlockIsUnsupported(); + return 0; +} + +TSAN_INTERCEPTOR(int, munlockall, void) { + MlockIsUnsupported(); + return 0; +} + +TSAN_INTERCEPTOR(int, fork, int fake) { + SCOPED_TSAN_INTERCEPTOR(fork, fake); + // It's intercepted merely to process pending signals. + int pid = REAL(fork)(fake); + if (pid == 0) { + // child + FdOnFork(thr, pc); + } else if (pid > 0) { + // parent + } + return pid; +} + +struct TsanInterceptorContext { + ThreadState *thr; + const uptr caller_pc; + const uptr pc; +}; + +#define COMMON_INTERCEPTOR_WRITE_RANGE(ctx, ptr, size) \ + MemoryAccessRange(((TsanInterceptorContext*)ctx)->thr, \ + ((TsanInterceptorContext*)ctx)->pc, \ + (uptr)ptr, size, true) +#define COMMON_INTERCEPTOR_READ_RANGE(ctx, ptr, size) \ + MemoryAccessRange(((TsanInterceptorContext*)ctx)->thr, \ + ((TsanInterceptorContext*)ctx)->pc, \ + (uptr)ptr, size, false) +#define COMMON_INTERCEPTOR_ENTER(ctx, func, ...) \ + SCOPED_TSAN_INTERCEPTOR(func, __VA_ARGS__) \ + TsanInterceptorContext _ctx = {thr, caller_pc, pc}; \ + ctx = (void*)&_ctx; \ + (void)ctx; +#define COMMON_INTERCEPTOR_FD_ACQUIRE(ctx, fd) \ + FdAcquire(((TsanInterceptorContext*)ctx)->thr, pc, fd) +#define COMMON_INTERCEPTOR_FD_RELEASE(ctx, fd) \ + FdRelease(((TsanInterceptorContext*)ctx)->thr, pc, fd) +#define COMMON_INTERCEPTOR_SET_THREAD_NAME(ctx, name) \ + ThreadSetName(((TsanInterceptorContext*)ctx)->thr, name) +#include "sanitizer_common/sanitizer_common_interceptors.inc" + +namespace __tsan { + +void ProcessPendingSignals(ThreadState *thr) { CHECK_EQ(thr->in_rtl, 0); SignalContext *sctx = SigCtx(thr); if (sctx == 0 || sctx->pending_signal_count == 0 || thr->in_signal_handler) return; + Context *ctx = CTX(); thr->in_signal_handler = true; sctx->pending_signal_count = 0; // These are too big for stack. @@ -1419,16 +1648,19 @@ static void process_pending_signals(ThreadState *thr) { sigactions[sig].sa_sigaction(sig, &signal->siginfo, &signal->ctx); else sigactions[sig].sa_handler(sig); - if (errno != 0) { + if (flags()->report_bugs && errno != 0) { ScopedInRtl in_rtl; - StackTrace stack; + __tsan::StackTrace stack; uptr pc = signal->sigaction ? (uptr)sigactions[sig].sa_sigaction : (uptr)sigactions[sig].sa_handler; stack.Init(&pc, 1); + Lock l(&ctx->thread_mtx); ScopedReport rep(ReportTypeErrnoInSignal); - rep.AddStack(&stack); - OutputReport(rep, rep.GetReport()->stacks[0]); + if (!IsFiredSuppression(ctx, rep, stack)) { + rep.AddStack(&stack); + OutputReport(ctx, rep, rep.GetReport()->stacks[0]); + } } errno = saved_errno; } @@ -1439,7 +1671,10 @@ static void process_pending_signals(ThreadState *thr) { thr->in_signal_handler = false; } -namespace __tsan { +static void unreachable() { + Printf("FATAL: ThreadSanitizer: unreachable called\n"); + Die(); +} void InitializeInterceptors() { CHECK_GT(cur_thread()->in_rtl, 0); @@ -1449,10 +1684,13 @@ void InitializeInterceptors() { REAL(memcpy) = internal_memcpy; REAL(memcmp) = internal_memcmp; + SANITIZER_COMMON_INTERCEPTORS_INIT; + TSAN_INTERCEPT(longjmp); TSAN_INTERCEPT(siglongjmp); TSAN_INTERCEPT(malloc); + TSAN_INTERCEPT(__libc_memalign); TSAN_INTERCEPT(calloc); TSAN_INTERCEPT(realloc); TSAN_INTERCEPT(free); @@ -1465,15 +1703,6 @@ void InitializeInterceptors() { TSAN_INTERCEPT(pvalloc); TSAN_INTERCEPT(posix_memalign); - TSAN_INTERCEPT(_Znwm); - TSAN_INTERCEPT(_ZnwmRKSt9nothrow_t); - TSAN_INTERCEPT(_Znam); - TSAN_INTERCEPT(_ZnamRKSt9nothrow_t); - TSAN_INTERCEPT(_ZdlPv); - TSAN_INTERCEPT(_ZdlPvRKSt9nothrow_t); - TSAN_INTERCEPT(_ZdaPv); - TSAN_INTERCEPT(_ZdaPvRKSt9nothrow_t); - TSAN_INTERCEPT(strlen); TSAN_INTERCEPT(memset); TSAN_INTERCEPT(memcpy); @@ -1490,9 +1719,6 @@ void InitializeInterceptors() { TSAN_INTERCEPT(strncpy); TSAN_INTERCEPT(strstr); - TSAN_INTERCEPT(__cxa_guard_acquire); - TSAN_INTERCEPT(__cxa_guard_release); - TSAN_INTERCEPT(pthread_create); TSAN_INTERCEPT(pthread_join); TSAN_INTERCEPT(pthread_detach); @@ -1520,7 +1746,7 @@ void InitializeInterceptors() { TSAN_INTERCEPT(pthread_rwlock_timedwrlock); TSAN_INTERCEPT(pthread_rwlock_unlock); - TSAN_INTERCEPT(pthread_cond_init); + // TSAN_INTERCEPT(pthread_cond_init); TSAN_INTERCEPT(pthread_cond_destroy); TSAN_INTERCEPT(pthread_cond_signal); TSAN_INTERCEPT(pthread_cond_broadcast); @@ -1541,14 +1767,30 @@ void InitializeInterceptors() { TSAN_INTERCEPT(sem_post); TSAN_INTERCEPT(sem_getvalue); - TSAN_INTERCEPT(read); - TSAN_INTERCEPT(pread); - TSAN_INTERCEPT(pread64); + TSAN_INTERCEPT(open); + TSAN_INTERCEPT(open64); + TSAN_INTERCEPT(creat); + TSAN_INTERCEPT(creat64); + TSAN_INTERCEPT(dup); + TSAN_INTERCEPT(dup2); + TSAN_INTERCEPT(dup3); + TSAN_INTERCEPT(eventfd); + TSAN_INTERCEPT(signalfd); + TSAN_INTERCEPT(inotify_init); + TSAN_INTERCEPT(inotify_init1); + TSAN_INTERCEPT(socket); + TSAN_INTERCEPT(socketpair); + TSAN_INTERCEPT(connect); + TSAN_INTERCEPT(accept); + TSAN_INTERCEPT(accept4); + TSAN_INTERCEPT(epoll_create); + TSAN_INTERCEPT(epoll_create1); + TSAN_INTERCEPT(close); + TSAN_INTERCEPT(pipe); + TSAN_INTERCEPT(pipe2); + TSAN_INTERCEPT(readv); TSAN_INTERCEPT(preadv64); - TSAN_INTERCEPT(write); - TSAN_INTERCEPT(pwrite); - TSAN_INTERCEPT(pwrite64); TSAN_INTERCEPT(writev); TSAN_INTERCEPT(pwritev64); TSAN_INTERCEPT(send); @@ -1558,6 +1800,8 @@ void InitializeInterceptors() { TSAN_INTERCEPT(unlink); TSAN_INTERCEPT(fopen); + TSAN_INTERCEPT(freopen); + TSAN_INTERCEPT(fclose); TSAN_INTERCEPT(fread); TSAN_INTERCEPT(fwrite); TSAN_INTERCEPT(puts); @@ -1566,25 +1810,42 @@ void InitializeInterceptors() { TSAN_INTERCEPT(epoll_ctl); TSAN_INTERCEPT(epoll_wait); + TSAN_INTERCEPT(poll); TSAN_INTERCEPT(sigaction); TSAN_INTERCEPT(signal); TSAN_INTERCEPT(raise); TSAN_INTERCEPT(kill); TSAN_INTERCEPT(pthread_kill); + TSAN_INTERCEPT(sleep); + TSAN_INTERCEPT(usleep); + TSAN_INTERCEPT(nanosleep); + TSAN_INTERCEPT(gettimeofday); + TSAN_INTERCEPT(mlock); + TSAN_INTERCEPT(munlock); + TSAN_INTERCEPT(mlockall); + TSAN_INTERCEPT(munlockall); + + TSAN_INTERCEPT(fork); + + // Need to setup it, because interceptors check that the function is resolved. + // But atexit is emitted directly into the module, so can't be resolved. + REAL(atexit) = (int(*)(void(*)()))unreachable; atexit_ctx = new(internal_alloc(MBlockAtExit, sizeof(AtExitContext))) AtExitContext(); if (__cxa_atexit(&finalize, 0, 0)) { - TsanPrintf("ThreadSanitizer: failed to setup atexit callback\n"); + Printf("ThreadSanitizer: failed to setup atexit callback\n"); Die(); } if (pthread_key_create(&g_thread_finalize_key, &thread_finalize)) { - TsanPrintf("ThreadSanitizer: failed to create thread key\n"); + Printf("ThreadSanitizer: failed to create thread key\n"); Die(); } + + FdInit(); } void internal_start_thread(void(*func)(void *arg), void *arg) { diff --git a/lib/tsan/rtl/tsan_interface.h b/lib/tsan/rtl/tsan_interface.h index ed21ec60544e..7480fc893f2d 100644 --- a/lib/tsan/rtl/tsan_interface.h +++ b/lib/tsan/rtl/tsan_interface.h @@ -16,6 +16,8 @@ #ifndef TSAN_INTERFACE_H #define TSAN_INTERFACE_H +#include <sanitizer/common_interface_defs.h> + // This header should NOT include any other headers. // All functions in this header are extern "C" and start with __tsan_. @@ -25,24 +27,30 @@ extern "C" { // This function should be called at the very beginning of the process, // before any instrumented code is executed and before any call to malloc. -void __tsan_init(); - -void __tsan_read1(void *addr); -void __tsan_read2(void *addr); -void __tsan_read4(void *addr); -void __tsan_read8(void *addr); -void __tsan_read16(void *addr); - -void __tsan_write1(void *addr); -void __tsan_write2(void *addr); -void __tsan_write4(void *addr); -void __tsan_write8(void *addr); -void __tsan_write16(void *addr); - -void __tsan_vptr_update(void **vptr_p, void *new_val); - -void __tsan_func_entry(void *call_pc); -void __tsan_func_exit(); +void __tsan_init() SANITIZER_INTERFACE_ATTRIBUTE; + +void __tsan_read1(void *addr) SANITIZER_INTERFACE_ATTRIBUTE; +void __tsan_read2(void *addr) SANITIZER_INTERFACE_ATTRIBUTE; +void __tsan_read4(void *addr) SANITIZER_INTERFACE_ATTRIBUTE; +void __tsan_read8(void *addr) SANITIZER_INTERFACE_ATTRIBUTE; +void __tsan_read16(void *addr) SANITIZER_INTERFACE_ATTRIBUTE; + +void __tsan_write1(void *addr) SANITIZER_INTERFACE_ATTRIBUTE; +void __tsan_write2(void *addr) SANITIZER_INTERFACE_ATTRIBUTE; +void __tsan_write4(void *addr) SANITIZER_INTERFACE_ATTRIBUTE; +void __tsan_write8(void *addr) SANITIZER_INTERFACE_ATTRIBUTE; +void __tsan_write16(void *addr) SANITIZER_INTERFACE_ATTRIBUTE; + +void __tsan_vptr_update(void **vptr_p, void *new_val) + SANITIZER_INTERFACE_ATTRIBUTE; + +void __tsan_func_entry(void *call_pc) SANITIZER_INTERFACE_ATTRIBUTE; +void __tsan_func_exit() SANITIZER_INTERFACE_ATTRIBUTE; + +void __tsan_read_range(void *addr, unsigned long size) // NOLINT + SANITIZER_INTERFACE_ATTRIBUTE; +void __tsan_write_range(void *addr, unsigned long size) // NOLINT + SANITIZER_INTERFACE_ATTRIBUTE; #ifdef __cplusplus } // extern "C" diff --git a/lib/tsan/rtl/tsan_interface_ann.cc b/lib/tsan/rtl/tsan_interface_ann.cc index a605b6c9d3f0..51ebbf2266dd 100644 --- a/lib/tsan/rtl/tsan_interface_ann.cc +++ b/lib/tsan/rtl/tsan_interface_ann.cc @@ -11,6 +11,7 @@ // //===----------------------------------------------------------------------===// #include "sanitizer_common/sanitizer_libc.h" +#include "sanitizer_common/sanitizer_internal_defs.h" #include "sanitizer_common/sanitizer_placement_new.h" #include "tsan_interface_ann.h" #include "tsan_mutex.h" @@ -52,11 +53,11 @@ class ScopedAnnotation { if (!flags()->enable_annotations) \ return; \ ThreadState *thr = cur_thread(); \ + const uptr pc = (uptr)__builtin_return_address(0); \ StatInc(thr, StatAnnotation); \ StatInc(thr, Stat##typ); \ ScopedAnnotation sa(thr, __FUNCTION__, f, l, \ (uptr)__builtin_return_address(0)); \ - const uptr pc = (uptr)&__FUNCTION__; \ (void)pc; \ /**/ @@ -159,73 +160,92 @@ bool IsExpectedReport(uptr addr, uptr size) { using namespace __tsan; // NOLINT extern "C" { -void AnnotateHappensBefore(char *f, int l, uptr addr) { +void INTERFACE_ATTRIBUTE AnnotateHappensBefore(char *f, int l, uptr addr) { SCOPED_ANNOTATION(AnnotateHappensBefore); Release(cur_thread(), CALLERPC, addr); } -void AnnotateHappensAfter(char *f, int l, uptr addr) { +void INTERFACE_ATTRIBUTE AnnotateHappensAfter(char *f, int l, uptr addr) { SCOPED_ANNOTATION(AnnotateHappensAfter); Acquire(cur_thread(), CALLERPC, addr); } -void AnnotateCondVarSignal(char *f, int l, uptr cv) { +void INTERFACE_ATTRIBUTE AnnotateCondVarSignal(char *f, int l, uptr cv) { SCOPED_ANNOTATION(AnnotateCondVarSignal); } -void AnnotateCondVarSignalAll(char *f, int l, uptr cv) { +void INTERFACE_ATTRIBUTE AnnotateCondVarSignalAll(char *f, int l, uptr cv) { SCOPED_ANNOTATION(AnnotateCondVarSignalAll); } -void AnnotateMutexIsNotPHB(char *f, int l, uptr mu) { +void INTERFACE_ATTRIBUTE AnnotateMutexIsNotPHB(char *f, int l, uptr mu) { SCOPED_ANNOTATION(AnnotateMutexIsNotPHB); } -void AnnotateCondVarWait(char *f, int l, uptr cv, uptr lock) { +void INTERFACE_ATTRIBUTE AnnotateCondVarWait(char *f, int l, uptr cv, + uptr lock) { SCOPED_ANNOTATION(AnnotateCondVarWait); } -void AnnotateRWLockCreate(char *f, int l, uptr lock) { +void INTERFACE_ATTRIBUTE AnnotateRWLockCreate(char *f, int l, uptr m) { SCOPED_ANNOTATION(AnnotateRWLockCreate); + MutexCreate(thr, pc, m, true, true, false); } -void AnnotateRWLockDestroy(char *f, int l, uptr lock) { +void INTERFACE_ATTRIBUTE AnnotateRWLockCreateStatic(char *f, int l, uptr m) { + SCOPED_ANNOTATION(AnnotateRWLockCreateStatic); + MutexCreate(thr, pc, m, true, true, true); +} + +void INTERFACE_ATTRIBUTE AnnotateRWLockDestroy(char *f, int l, uptr m) { SCOPED_ANNOTATION(AnnotateRWLockDestroy); + MutexDestroy(thr, pc, m); } -void AnnotateRWLockAcquired(char *f, int l, uptr lock, uptr is_w) { +void INTERFACE_ATTRIBUTE AnnotateRWLockAcquired(char *f, int l, uptr m, + uptr is_w) { SCOPED_ANNOTATION(AnnotateRWLockAcquired); + if (is_w) + MutexLock(thr, pc, m); + else + MutexReadLock(thr, pc, m); } -void AnnotateRWLockReleased(char *f, int l, uptr lock, uptr is_w) { +void INTERFACE_ATTRIBUTE AnnotateRWLockReleased(char *f, int l, uptr m, + uptr is_w) { SCOPED_ANNOTATION(AnnotateRWLockReleased); + if (is_w) + MutexUnlock(thr, pc, m); + else + MutexReadUnlock(thr, pc, m); } -void AnnotateTraceMemory(char *f, int l, uptr mem) { +void INTERFACE_ATTRIBUTE AnnotateTraceMemory(char *f, int l, uptr mem) { SCOPED_ANNOTATION(AnnotateTraceMemory); } -void AnnotateFlushState(char *f, int l) { +void INTERFACE_ATTRIBUTE AnnotateFlushState(char *f, int l) { SCOPED_ANNOTATION(AnnotateFlushState); } -void AnnotateNewMemory(char *f, int l, uptr mem, uptr size) { +void INTERFACE_ATTRIBUTE AnnotateNewMemory(char *f, int l, uptr mem, + uptr size) { SCOPED_ANNOTATION(AnnotateNewMemory); } -void AnnotateNoOp(char *f, int l, uptr mem) { +void INTERFACE_ATTRIBUTE AnnotateNoOp(char *f, int l, uptr mem) { SCOPED_ANNOTATION(AnnotateNoOp); } static void ReportMissedExpectedRace(ExpectRace *race) { - TsanPrintf("==================\n"); - TsanPrintf("WARNING: ThreadSanitizer: missed expected data race\n"); - TsanPrintf(" %s addr=%zx %s:%d\n", + Printf("==================\n"); + Printf("WARNING: ThreadSanitizer: missed expected data race\n"); + Printf(" %s addr=%zx %s:%d\n", race->desc, race->addr, race->file, race->line); - TsanPrintf("==================\n"); + Printf("==================\n"); } -void AnnotateFlushExpectedRaces(char *f, int l) { +void INTERFACE_ATTRIBUTE AnnotateFlushExpectedRaces(char *f, int l) { SCOPED_ANNOTATION(AnnotateFlushExpectedRaces); Lock lock(&dyn_ann_ctx->mtx); while (dyn_ann_ctx->expect.next != &dyn_ann_ctx->expect) { @@ -240,32 +260,39 @@ void AnnotateFlushExpectedRaces(char *f, int l) { } } -void AnnotateEnableRaceDetection(char *f, int l, int enable) { +void INTERFACE_ATTRIBUTE AnnotateEnableRaceDetection( + char *f, int l, int enable) { SCOPED_ANNOTATION(AnnotateEnableRaceDetection); // FIXME: Reconsider this functionality later. It may be irrelevant. } -void AnnotateMutexIsUsedAsCondVar(char *f, int l, uptr mu) { +void INTERFACE_ATTRIBUTE AnnotateMutexIsUsedAsCondVar( + char *f, int l, uptr mu) { SCOPED_ANNOTATION(AnnotateMutexIsUsedAsCondVar); } -void AnnotatePCQGet(char *f, int l, uptr pcq) { +void INTERFACE_ATTRIBUTE AnnotatePCQGet( + char *f, int l, uptr pcq) { SCOPED_ANNOTATION(AnnotatePCQGet); } -void AnnotatePCQPut(char *f, int l, uptr pcq) { +void INTERFACE_ATTRIBUTE AnnotatePCQPut( + char *f, int l, uptr pcq) { SCOPED_ANNOTATION(AnnotatePCQPut); } -void AnnotatePCQDestroy(char *f, int l, uptr pcq) { +void INTERFACE_ATTRIBUTE AnnotatePCQDestroy( + char *f, int l, uptr pcq) { SCOPED_ANNOTATION(AnnotatePCQDestroy); } -void AnnotatePCQCreate(char *f, int l, uptr pcq) { +void INTERFACE_ATTRIBUTE AnnotatePCQCreate( + char *f, int l, uptr pcq) { SCOPED_ANNOTATION(AnnotatePCQCreate); } -void AnnotateExpectRace(char *f, int l, uptr mem, char *desc) { +void INTERFACE_ATTRIBUTE AnnotateExpectRace( + char *f, int l, uptr mem, char *desc) { SCOPED_ANNOTATION(AnnotateExpectRace); Lock lock(&dyn_ann_ctx->mtx); AddExpectRace(&dyn_ann_ctx->expect, @@ -273,7 +300,8 @@ void AnnotateExpectRace(char *f, int l, uptr mem, char *desc) { DPrintf("Add expected race: %s addr=%zx %s:%d\n", desc, mem, f, l); } -static void BenignRaceImpl(char *f, int l, uptr mem, uptr size, char *desc) { +static void BenignRaceImpl( + char *f, int l, uptr mem, uptr size, char *desc) { Lock lock(&dyn_ann_ctx->mtx); AddExpectRace(&dyn_ann_ctx->benign, f, l, mem, size, desc); @@ -281,69 +309,76 @@ static void BenignRaceImpl(char *f, int l, uptr mem, uptr size, char *desc) { } // FIXME: Turn it off later. WTF is benign race?1?? Go talk to Hans Boehm. -void AnnotateBenignRaceSized(char *f, int l, uptr mem, uptr size, char *desc) { +void INTERFACE_ATTRIBUTE AnnotateBenignRaceSized( + char *f, int l, uptr mem, uptr size, char *desc) { SCOPED_ANNOTATION(AnnotateBenignRaceSized); BenignRaceImpl(f, l, mem, size, desc); } -void AnnotateBenignRace(char *f, int l, uptr mem, char *desc) { +void INTERFACE_ATTRIBUTE AnnotateBenignRace( + char *f, int l, uptr mem, char *desc) { SCOPED_ANNOTATION(AnnotateBenignRace); BenignRaceImpl(f, l, mem, 1, desc); } -void AnnotateIgnoreReadsBegin(char *f, int l) { +void INTERFACE_ATTRIBUTE AnnotateIgnoreReadsBegin(char *f, int l) { SCOPED_ANNOTATION(AnnotateIgnoreReadsBegin); IgnoreCtl(cur_thread(), false, true); } -void AnnotateIgnoreReadsEnd(char *f, int l) { +void INTERFACE_ATTRIBUTE AnnotateIgnoreReadsEnd(char *f, int l) { SCOPED_ANNOTATION(AnnotateIgnoreReadsEnd); IgnoreCtl(cur_thread(), false, false); } -void AnnotateIgnoreWritesBegin(char *f, int l) { +void INTERFACE_ATTRIBUTE AnnotateIgnoreWritesBegin(char *f, int l) { SCOPED_ANNOTATION(AnnotateIgnoreWritesBegin); IgnoreCtl(cur_thread(), true, true); } -void AnnotateIgnoreWritesEnd(char *f, int l) { +void INTERFACE_ATTRIBUTE AnnotateIgnoreWritesEnd(char *f, int l) { SCOPED_ANNOTATION(AnnotateIgnoreWritesEnd); - IgnoreCtl(cur_thread(), true, false); + IgnoreCtl(thr, true, false); } -void AnnotatePublishMemoryRange(char *f, int l, uptr addr, uptr size) { +void INTERFACE_ATTRIBUTE AnnotatePublishMemoryRange( + char *f, int l, uptr addr, uptr size) { SCOPED_ANNOTATION(AnnotatePublishMemoryRange); } -void AnnotateUnpublishMemoryRange(char *f, int l, uptr addr, uptr size) { +void INTERFACE_ATTRIBUTE AnnotateUnpublishMemoryRange( + char *f, int l, uptr addr, uptr size) { SCOPED_ANNOTATION(AnnotateUnpublishMemoryRange); } -void AnnotateThreadName(char *f, int l, char *name) { +void INTERFACE_ATTRIBUTE AnnotateThreadName( + char *f, int l, char *name) { SCOPED_ANNOTATION(AnnotateThreadName); + ThreadSetName(thr, name); } -void WTFAnnotateHappensBefore(char *f, int l, uptr addr) { +void INTERFACE_ATTRIBUTE WTFAnnotateHappensBefore(char *f, int l, uptr addr) { SCOPED_ANNOTATION(AnnotateHappensBefore); } -void WTFAnnotateHappensAfter(char *f, int l, uptr addr) { +void INTERFACE_ATTRIBUTE WTFAnnotateHappensAfter(char *f, int l, uptr addr) { SCOPED_ANNOTATION(AnnotateHappensAfter); } -void WTFAnnotateBenignRaceSized(char *f, int l, uptr mem, uptr sz, char *desc) { +void INTERFACE_ATTRIBUTE WTFAnnotateBenignRaceSized( + char *f, int l, uptr mem, uptr sz, char *desc) { SCOPED_ANNOTATION(AnnotateBenignRaceSized); } -int RunningOnValgrind() { +int INTERFACE_ATTRIBUTE RunningOnValgrind() { return flags()->running_on_valgrind; } -double __attribute__((weak)) ValgrindSlowdown(void) { +double __attribute__((weak)) INTERFACE_ATTRIBUTE ValgrindSlowdown(void) { return 10.0; } -const char *ThreadSanitizerQuery(const char *query) { +const char INTERFACE_ATTRIBUTE* ThreadSanitizerQuery(const char *query) { if (internal_strcmp(query, "pure_happens_before") == 0) return "1"; else diff --git a/lib/tsan/rtl/tsan_interface_ann.h b/lib/tsan/rtl/tsan_interface_ann.h index 09e807a0087d..ed809073327e 100644 --- a/lib/tsan/rtl/tsan_interface_ann.h +++ b/lib/tsan/rtl/tsan_interface_ann.h @@ -14,6 +14,8 @@ #ifndef TSAN_INTERFACE_ANN_H #define TSAN_INTERFACE_ANN_H +#include <sanitizer/common_interface_defs.h> + // This header should NOT include any other headers. // All functions in this header are extern "C" and start with __tsan_. @@ -21,8 +23,8 @@ extern "C" { #endif -void __tsan_acquire(void *addr); -void __tsan_release(void *addr); +void __tsan_acquire(void *addr) SANITIZER_INTERFACE_ATTRIBUTE; +void __tsan_release(void *addr) SANITIZER_INTERFACE_ATTRIBUTE; #ifdef __cplusplus } // extern "C" diff --git a/lib/tsan/rtl/tsan_interface_atomic.cc b/lib/tsan/rtl/tsan_interface_atomic.cc index a3982a161b9b..a9d75e5bf76c 100644 --- a/lib/tsan/rtl/tsan_interface_atomic.cc +++ b/lib/tsan/rtl/tsan_interface_atomic.cc @@ -11,6 +11,14 @@ // //===----------------------------------------------------------------------===// +// ThreadSanitizer atomic operations are based on C++11/C1x standards. +// For background see C++11 standard. A slightly older, publically +// available draft of the standard (not entirely up-to-date, but close enough +// for casual browsing) is available here: +// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3242.pdf +// The following page contains more background information: +// http://www.hpl.hp.com/personal/Hans_Boehm/c++mm/ + #include "sanitizer_common/sanitizer_placement_new.h" #include "tsan_interface_atomic.h" #include "tsan_flags.h" @@ -39,12 +47,13 @@ typedef __tsan_atomic8 a8; typedef __tsan_atomic16 a16; typedef __tsan_atomic32 a32; typedef __tsan_atomic64 a64; -const int mo_relaxed = __tsan_memory_order_relaxed; -const int mo_consume = __tsan_memory_order_consume; -const int mo_acquire = __tsan_memory_order_acquire; -const int mo_release = __tsan_memory_order_release; -const int mo_acq_rel = __tsan_memory_order_acq_rel; -const int mo_seq_cst = __tsan_memory_order_seq_cst; +typedef __tsan_atomic128 a128; +const morder mo_relaxed = __tsan_memory_order_relaxed; +const morder mo_consume = __tsan_memory_order_consume; +const morder mo_acquire = __tsan_memory_order_acquire; +const morder mo_release = __tsan_memory_order_release; +const morder mo_acq_rel = __tsan_memory_order_acq_rel; +const morder mo_seq_cst = __tsan_memory_order_seq_cst; static void AtomicStatInc(ThreadState *thr, uptr size, morder mo, StatType t) { StatInc(thr, StatAtomic); @@ -52,7 +61,8 @@ static void AtomicStatInc(ThreadState *thr, uptr size, morder mo, StatType t) { StatInc(thr, size == 1 ? StatAtomic1 : size == 2 ? StatAtomic2 : size == 4 ? StatAtomic4 - : StatAtomic8); + : size == 8 ? StatAtomic8 + : StatAtomic16); StatInc(thr, mo == mo_relaxed ? StatAtomicRelaxed : mo == mo_consume ? StatAtomicConsume : mo == mo_acquire ? StatAtomicAcquire @@ -61,9 +71,152 @@ static void AtomicStatInc(ThreadState *thr, uptr size, morder mo, StatType t) { : StatAtomicSeq_Cst); } +static bool IsLoadOrder(morder mo) { + return mo == mo_relaxed || mo == mo_consume + || mo == mo_acquire || mo == mo_seq_cst; +} + +static bool IsStoreOrder(morder mo) { + return mo == mo_relaxed || mo == mo_release || mo == mo_seq_cst; +} + +static bool IsReleaseOrder(morder mo) { + return mo == mo_release || mo == mo_acq_rel || mo == mo_seq_cst; +} + +static bool IsAcquireOrder(morder mo) { + return mo == mo_consume || mo == mo_acquire + || mo == mo_acq_rel || mo == mo_seq_cst; +} + +static bool IsAcqRelOrder(morder mo) { + return mo == mo_acq_rel || mo == mo_seq_cst; +} + +static morder ConvertOrder(morder mo) { + if (mo > (morder)100500) { + mo = morder(mo - 100500); + if (mo == morder(1 << 0)) + mo = mo_relaxed; + else if (mo == morder(1 << 1)) + mo = mo_consume; + else if (mo == morder(1 << 2)) + mo = mo_acquire; + else if (mo == morder(1 << 3)) + mo = mo_release; + else if (mo == morder(1 << 4)) + mo = mo_acq_rel; + else if (mo == morder(1 << 5)) + mo = mo_seq_cst; + } + CHECK_GE(mo, mo_relaxed); + CHECK_LE(mo, mo_seq_cst); + return mo; +} + +template<typename T> T func_xchg(volatile T *v, T op) { + T res = __sync_lock_test_and_set(v, op); + // __sync_lock_test_and_set does not contain full barrier. + __sync_synchronize(); + return res; +} + +template<typename T> T func_add(volatile T *v, T op) { + return __sync_fetch_and_add(v, op); +} + +template<typename T> T func_sub(volatile T *v, T op) { + return __sync_fetch_and_sub(v, op); +} + +template<typename T> T func_and(volatile T *v, T op) { + return __sync_fetch_and_and(v, op); +} + +template<typename T> T func_or(volatile T *v, T op) { + return __sync_fetch_and_or(v, op); +} + +template<typename T> T func_xor(volatile T *v, T op) { + return __sync_fetch_and_xor(v, op); +} + +template<typename T> T func_nand(volatile T *v, T op) { + // clang does not support __sync_fetch_and_nand. + T cmp = *v; + for (;;) { + T newv = ~(cmp & op); + T cur = __sync_val_compare_and_swap(v, cmp, newv); + if (cmp == cur) + return cmp; + cmp = cur; + } +} + +template<typename T> T func_cas(volatile T *v, T cmp, T xch) { + return __sync_val_compare_and_swap(v, cmp, xch); +} + +// clang does not support 128-bit atomic ops. +// Atomic ops are executed under tsan internal mutex, +// here we assume that the atomic variables are not accessed +// from non-instrumented code. +#ifndef __GCC_HAVE_SYNC_COMPARE_AND_SWAP_16 +a128 func_xchg(volatile a128 *v, a128 op) { + a128 cmp = *v; + *v = op; + return cmp; +} + +a128 func_add(volatile a128 *v, a128 op) { + a128 cmp = *v; + *v = cmp + op; + return cmp; +} + +a128 func_sub(volatile a128 *v, a128 op) { + a128 cmp = *v; + *v = cmp - op; + return cmp; +} + +a128 func_and(volatile a128 *v, a128 op) { + a128 cmp = *v; + *v = cmp & op; + return cmp; +} + +a128 func_or(volatile a128 *v, a128 op) { + a128 cmp = *v; + *v = cmp | op; + return cmp; +} + +a128 func_xor(volatile a128 *v, a128 op) { + a128 cmp = *v; + *v = cmp ^ op; + return cmp; +} + +a128 func_nand(volatile a128 *v, a128 op) { + a128 cmp = *v; + *v = ~(cmp & op); + return cmp; +} + +a128 func_cas(volatile a128 *v, a128 cmp, a128 xch) { + a128 cur = *v; + if (cur == cmp) + *v = xch; + return cur; +} +#endif + #define SCOPED_ATOMIC(func, ...) \ + mo = ConvertOrder(mo); \ mo = flags()->force_seq_cst_atomics ? (morder)mo_seq_cst : mo; \ ThreadState *const thr = cur_thread(); \ + ProcessPendingSignals(thr); \ const uptr pc = (uptr)__builtin_return_address(0); \ AtomicStatInc(thr, sizeof(*a), mo, StatAtomic##func); \ ScopedAtomic sa(thr, pc, __FUNCTION__); \ @@ -73,93 +226,130 @@ static void AtomicStatInc(ThreadState *thr, uptr size, morder mo, StatType t) { template<typename T> static T AtomicLoad(ThreadState *thr, uptr pc, const volatile T *a, morder mo) { - CHECK(mo & (mo_relaxed | mo_consume | mo_acquire | mo_seq_cst)); + CHECK(IsLoadOrder(mo)); + // This fast-path is critical for performance. + // Assume the access is atomic. + if (!IsAcquireOrder(mo) && sizeof(T) <= sizeof(a)) + return *a; + SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, (uptr)a, false); + thr->clock.set(thr->tid, thr->fast_state.epoch()); + thr->clock.acquire(&s->clock); T v = *a; - if (mo & (mo_consume | mo_acquire | mo_seq_cst)) - Acquire(thr, pc, (uptr)a); + s->mtx.ReadUnlock(); + __sync_synchronize(); return v; } template<typename T> static void AtomicStore(ThreadState *thr, uptr pc, volatile T *a, T v, morder mo) { - CHECK(mo & (mo_relaxed | mo_release | mo_seq_cst)); - if (mo & (mo_release | mo_seq_cst)) - Release(thr, pc, (uptr)a); + CHECK(IsStoreOrder(mo)); + // This fast-path is critical for performance. + // Assume the access is atomic. + // Strictly saying even relaxed store cuts off release sequence, + // so must reset the clock. + if (!IsReleaseOrder(mo) && sizeof(T) <= sizeof(a)) { + *a = v; + return; + } + __sync_synchronize(); + SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, (uptr)a, true); + thr->clock.set(thr->tid, thr->fast_state.epoch()); + thr->clock.ReleaseStore(&s->clock); *a = v; + s->mtx.Unlock(); + // Trainling memory barrier to provide sequential consistency + // for Dekker-like store-load synchronization. + __sync_synchronize(); +} + +template<typename T, T (*F)(volatile T *v, T op)> +static T AtomicRMW(ThreadState *thr, uptr pc, volatile T *a, T v, morder mo) { + SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, (uptr)a, true); + thr->clock.set(thr->tid, thr->fast_state.epoch()); + if (IsAcqRelOrder(mo)) + thr->clock.acq_rel(&s->clock); + else if (IsReleaseOrder(mo)) + thr->clock.release(&s->clock); + else if (IsAcquireOrder(mo)) + thr->clock.acquire(&s->clock); + v = F(a, v); + s->mtx.Unlock(); + return v; } template<typename T> static T AtomicExchange(ThreadState *thr, uptr pc, volatile T *a, T v, morder mo) { - if (mo & (mo_release | mo_acq_rel | mo_seq_cst)) - Release(thr, pc, (uptr)a); - v = __sync_lock_test_and_set(a, v); - if (mo & (mo_consume | mo_acquire | mo_acq_rel | mo_seq_cst)) - Acquire(thr, pc, (uptr)a); - return v; + return AtomicRMW<T, func_xchg>(thr, pc, a, v, mo); } template<typename T> static T AtomicFetchAdd(ThreadState *thr, uptr pc, volatile T *a, T v, morder mo) { - if (mo & (mo_release | mo_acq_rel | mo_seq_cst)) - Release(thr, pc, (uptr)a); - v = __sync_fetch_and_add(a, v); - if (mo & (mo_consume | mo_acquire | mo_acq_rel | mo_seq_cst)) - Acquire(thr, pc, (uptr)a); - return v; + return AtomicRMW<T, func_add>(thr, pc, a, v, mo); +} + +template<typename T> +static T AtomicFetchSub(ThreadState *thr, uptr pc, volatile T *a, T v, + morder mo) { + return AtomicRMW<T, func_sub>(thr, pc, a, v, mo); } template<typename T> static T AtomicFetchAnd(ThreadState *thr, uptr pc, volatile T *a, T v, morder mo) { - if (mo & (mo_release | mo_acq_rel | mo_seq_cst)) - Release(thr, pc, (uptr)a); - v = __sync_fetch_and_and(a, v); - if (mo & (mo_consume | mo_acquire | mo_acq_rel | mo_seq_cst)) - Acquire(thr, pc, (uptr)a); - return v; + return AtomicRMW<T, func_and>(thr, pc, a, v, mo); } template<typename T> static T AtomicFetchOr(ThreadState *thr, uptr pc, volatile T *a, T v, morder mo) { - if (mo & (mo_release | mo_acq_rel | mo_seq_cst)) - Release(thr, pc, (uptr)a); - v = __sync_fetch_and_or(a, v); - if (mo & (mo_consume | mo_acquire | mo_acq_rel | mo_seq_cst)) - Acquire(thr, pc, (uptr)a); - return v; + return AtomicRMW<T, func_or>(thr, pc, a, v, mo); } template<typename T> static T AtomicFetchXor(ThreadState *thr, uptr pc, volatile T *a, T v, morder mo) { - if (mo & (mo_release | mo_acq_rel | mo_seq_cst)) - Release(thr, pc, (uptr)a); - v = __sync_fetch_and_xor(a, v); - if (mo & (mo_consume | mo_acquire | mo_acq_rel | mo_seq_cst)) - Acquire(thr, pc, (uptr)a); - return v; + return AtomicRMW<T, func_xor>(thr, pc, a, v, mo); +} + +template<typename T> +static T AtomicFetchNand(ThreadState *thr, uptr pc, volatile T *a, T v, + morder mo) { + return AtomicRMW<T, func_nand>(thr, pc, a, v, mo); } template<typename T> static bool AtomicCAS(ThreadState *thr, uptr pc, - volatile T *a, T *c, T v, morder mo) { - if (mo & (mo_release | mo_acq_rel | mo_seq_cst)) - Release(thr, pc, (uptr)a); + volatile T *a, T *c, T v, morder mo, morder fmo) { + (void)fmo; // Unused because llvm does not pass it yet. + SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, (uptr)a, true); + thr->clock.set(thr->tid, thr->fast_state.epoch()); + if (IsAcqRelOrder(mo)) + thr->clock.acq_rel(&s->clock); + else if (IsReleaseOrder(mo)) + thr->clock.release(&s->clock); + else if (IsAcquireOrder(mo)) + thr->clock.acquire(&s->clock); T cc = *c; - T pr = __sync_val_compare_and_swap(a, cc, v); - if (mo & (mo_consume | mo_acquire | mo_acq_rel | mo_seq_cst)) - Acquire(thr, pc, (uptr)a); + T pr = func_cas(a, cc, v); + s->mtx.Unlock(); if (pr == cc) return true; *c = pr; return false; } +template<typename T> +static T AtomicCAS(ThreadState *thr, uptr pc, + volatile T *a, T c, T v, morder mo, morder fmo) { + AtomicCAS(thr, pc, a, &c, v, mo, fmo); + return c; +} + static void AtomicFence(ThreadState *thr, uptr pc, morder mo) { + // FIXME(dvyukov): not implemented. __sync_synchronize(); } @@ -179,6 +369,12 @@ a64 __tsan_atomic64_load(const volatile a64 *a, morder mo) { SCOPED_ATOMIC(Load, a, mo); } +#if __TSAN_HAS_INT128 +a128 __tsan_atomic128_load(const volatile a128 *a, morder mo) { + SCOPED_ATOMIC(Load, a, mo); +} +#endif + void __tsan_atomic8_store(volatile a8 *a, a8 v, morder mo) { SCOPED_ATOMIC(Store, a, v, mo); } @@ -195,6 +391,12 @@ void __tsan_atomic64_store(volatile a64 *a, a64 v, morder mo) { SCOPED_ATOMIC(Store, a, v, mo); } +#if __TSAN_HAS_INT128 +void __tsan_atomic128_store(volatile a128 *a, a128 v, morder mo) { + SCOPED_ATOMIC(Store, a, v, mo); +} +#endif + a8 __tsan_atomic8_exchange(volatile a8 *a, a8 v, morder mo) { SCOPED_ATOMIC(Exchange, a, v, mo); } @@ -211,6 +413,12 @@ a64 __tsan_atomic64_exchange(volatile a64 *a, a64 v, morder mo) { SCOPED_ATOMIC(Exchange, a, v, mo); } +#if __TSAN_HAS_INT128 +a128 __tsan_atomic128_exchange(volatile a128 *a, a128 v, morder mo) { + SCOPED_ATOMIC(Exchange, a, v, mo); +} +#endif + a8 __tsan_atomic8_fetch_add(volatile a8 *a, a8 v, morder mo) { SCOPED_ATOMIC(FetchAdd, a, v, mo); } @@ -227,6 +435,34 @@ a64 __tsan_atomic64_fetch_add(volatile a64 *a, a64 v, morder mo) { SCOPED_ATOMIC(FetchAdd, a, v, mo); } +#if __TSAN_HAS_INT128 +a128 __tsan_atomic128_fetch_add(volatile a128 *a, a128 v, morder mo) { + SCOPED_ATOMIC(FetchAdd, a, v, mo); +} +#endif + +a8 __tsan_atomic8_fetch_sub(volatile a8 *a, a8 v, morder mo) { + SCOPED_ATOMIC(FetchSub, a, v, mo); +} + +a16 __tsan_atomic16_fetch_sub(volatile a16 *a, a16 v, morder mo) { + SCOPED_ATOMIC(FetchSub, a, v, mo); +} + +a32 __tsan_atomic32_fetch_sub(volatile a32 *a, a32 v, morder mo) { + SCOPED_ATOMIC(FetchSub, a, v, mo); +} + +a64 __tsan_atomic64_fetch_sub(volatile a64 *a, a64 v, morder mo) { + SCOPED_ATOMIC(FetchSub, a, v, mo); +} + +#if __TSAN_HAS_INT128 +a128 __tsan_atomic128_fetch_sub(volatile a128 *a, a128 v, morder mo) { + SCOPED_ATOMIC(FetchSub, a, v, mo); +} +#endif + a8 __tsan_atomic8_fetch_and(volatile a8 *a, a8 v, morder mo) { SCOPED_ATOMIC(FetchAnd, a, v, mo); } @@ -243,6 +479,12 @@ a64 __tsan_atomic64_fetch_and(volatile a64 *a, a64 v, morder mo) { SCOPED_ATOMIC(FetchAnd, a, v, mo); } +#if __TSAN_HAS_INT128 +a128 __tsan_atomic128_fetch_and(volatile a128 *a, a128 v, morder mo) { + SCOPED_ATOMIC(FetchAnd, a, v, mo); +} +#endif + a8 __tsan_atomic8_fetch_or(volatile a8 *a, a8 v, morder mo) { SCOPED_ATOMIC(FetchOr, a, v, mo); } @@ -259,6 +501,12 @@ a64 __tsan_atomic64_fetch_or(volatile a64 *a, a64 v, morder mo) { SCOPED_ATOMIC(FetchOr, a, v, mo); } +#if __TSAN_HAS_INT128 +a128 __tsan_atomic128_fetch_or(volatile a128 *a, a128 v, morder mo) { + SCOPED_ATOMIC(FetchOr, a, v, mo); +} +#endif + a8 __tsan_atomic8_fetch_xor(volatile a8 *a, a8 v, morder mo) { SCOPED_ATOMIC(FetchXor, a, v, mo); } @@ -275,47 +523,118 @@ a64 __tsan_atomic64_fetch_xor(volatile a64 *a, a64 v, morder mo) { SCOPED_ATOMIC(FetchXor, a, v, mo); } +#if __TSAN_HAS_INT128 +a128 __tsan_atomic128_fetch_xor(volatile a128 *a, a128 v, morder mo) { + SCOPED_ATOMIC(FetchXor, a, v, mo); +} +#endif + +a8 __tsan_atomic8_fetch_nand(volatile a8 *a, a8 v, morder mo) { + SCOPED_ATOMIC(FetchNand, a, v, mo); +} + +a16 __tsan_atomic16_fetch_nand(volatile a16 *a, a16 v, morder mo) { + SCOPED_ATOMIC(FetchNand, a, v, mo); +} + +a32 __tsan_atomic32_fetch_nand(volatile a32 *a, a32 v, morder mo) { + SCOPED_ATOMIC(FetchNand, a, v, mo); +} + +a64 __tsan_atomic64_fetch_nand(volatile a64 *a, a64 v, morder mo) { + SCOPED_ATOMIC(FetchNand, a, v, mo); +} + +#if __TSAN_HAS_INT128 +a128 __tsan_atomic128_fetch_nand(volatile a128 *a, a128 v, morder mo) { + SCOPED_ATOMIC(FetchNand, a, v, mo); +} +#endif + int __tsan_atomic8_compare_exchange_strong(volatile a8 *a, a8 *c, a8 v, - morder mo) { - SCOPED_ATOMIC(CAS, a, c, v, mo); + morder mo, morder fmo) { + SCOPED_ATOMIC(CAS, a, c, v, mo, fmo); } int __tsan_atomic16_compare_exchange_strong(volatile a16 *a, a16 *c, a16 v, - morder mo) { - SCOPED_ATOMIC(CAS, a, c, v, mo); + morder mo, morder fmo) { + SCOPED_ATOMIC(CAS, a, c, v, mo, fmo); } int __tsan_atomic32_compare_exchange_strong(volatile a32 *a, a32 *c, a32 v, - morder mo) { - SCOPED_ATOMIC(CAS, a, c, v, mo); + morder mo, morder fmo) { + SCOPED_ATOMIC(CAS, a, c, v, mo, fmo); } int __tsan_atomic64_compare_exchange_strong(volatile a64 *a, a64 *c, a64 v, - morder mo) { - SCOPED_ATOMIC(CAS, a, c, v, mo); + morder mo, morder fmo) { + SCOPED_ATOMIC(CAS, a, c, v, mo, fmo); +} + +#if __TSAN_HAS_INT128 +int __tsan_atomic128_compare_exchange_strong(volatile a128 *a, a128 *c, a128 v, + morder mo, morder fmo) { + SCOPED_ATOMIC(CAS, a, c, v, mo, fmo); } +#endif int __tsan_atomic8_compare_exchange_weak(volatile a8 *a, a8 *c, a8 v, - morder mo) { - SCOPED_ATOMIC(CAS, a, c, v, mo); + morder mo, morder fmo) { + SCOPED_ATOMIC(CAS, a, c, v, mo, fmo); } int __tsan_atomic16_compare_exchange_weak(volatile a16 *a, a16 *c, a16 v, - morder mo) { - SCOPED_ATOMIC(CAS, a, c, v, mo); + morder mo, morder fmo) { + SCOPED_ATOMIC(CAS, a, c, v, mo, fmo); } int __tsan_atomic32_compare_exchange_weak(volatile a32 *a, a32 *c, a32 v, - morder mo) { - SCOPED_ATOMIC(CAS, a, c, v, mo); + morder mo, morder fmo) { + SCOPED_ATOMIC(CAS, a, c, v, mo, fmo); } int __tsan_atomic64_compare_exchange_weak(volatile a64 *a, a64 *c, a64 v, - morder mo) { - SCOPED_ATOMIC(CAS, a, c, v, mo); + morder mo, morder fmo) { + SCOPED_ATOMIC(CAS, a, c, v, mo, fmo); +} + +#if __TSAN_HAS_INT128 +int __tsan_atomic128_compare_exchange_weak(volatile a128 *a, a128 *c, a128 v, + morder mo, morder fmo) { + SCOPED_ATOMIC(CAS, a, c, v, mo, fmo); +} +#endif + +a8 __tsan_atomic8_compare_exchange_val(volatile a8 *a, a8 c, a8 v, + morder mo, morder fmo) { + SCOPED_ATOMIC(CAS, a, c, v, mo, fmo); +} +a16 __tsan_atomic16_compare_exchange_val(volatile a16 *a, a16 c, a16 v, + morder mo, morder fmo) { + SCOPED_ATOMIC(CAS, a, c, v, mo, fmo); } +a32 __tsan_atomic32_compare_exchange_val(volatile a32 *a, a32 c, a32 v, + morder mo, morder fmo) { + SCOPED_ATOMIC(CAS, a, c, v, mo, fmo); +} + +a64 __tsan_atomic64_compare_exchange_val(volatile a64 *a, a64 c, a64 v, + morder mo, morder fmo) { + SCOPED_ATOMIC(CAS, a, c, v, mo, fmo); +} + +#if __TSAN_HAS_INT128 +a128 __tsan_atomic64_compare_exchange_val(volatile a128 *a, a128 c, a128 v, + morder mo, morder fmo) { + SCOPED_ATOMIC(CAS, a, c, v, mo, fmo); +} +#endif + void __tsan_atomic_thread_fence(morder mo) { char* a; SCOPED_ATOMIC(Fence, mo); } + +void __tsan_atomic_signal_fence(morder mo) { +} diff --git a/lib/tsan/rtl/tsan_interface_atomic.h b/lib/tsan/rtl/tsan_interface_atomic.h index dff32b1473f4..5352d56679f7 100644 --- a/lib/tsan/rtl/tsan_interface_atomic.h +++ b/lib/tsan/rtl/tsan_interface_atomic.h @@ -13,109 +13,193 @@ #ifndef TSAN_INTERFACE_ATOMIC_H #define TSAN_INTERFACE_ATOMIC_H +#ifndef INTERFACE_ATTRIBUTE +# define INTERFACE_ATTRIBUTE __attribute__((visibility("default"))) +#endif + #ifdef __cplusplus extern "C" { #endif -typedef char __tsan_atomic8; -typedef short __tsan_atomic16; // NOLINT -typedef int __tsan_atomic32; -typedef long __tsan_atomic64; // NOLINT +typedef char __tsan_atomic8; +typedef short __tsan_atomic16; // NOLINT +typedef int __tsan_atomic32; +typedef long __tsan_atomic64; // NOLINT + +#if defined(__SIZEOF_INT128__) \ + || (__clang_major__ * 100 + __clang_minor__ >= 302) +__extension__ typedef __int128 __tsan_atomic128; +#define __TSAN_HAS_INT128 1 +#else +typedef char __tsan_atomic128; +#define __TSAN_HAS_INT128 0 +#endif +// Part of ABI, do not change. +// http://llvm.org/viewvc/llvm-project/libcxx/trunk/include/atomic?view=markup typedef enum { - __tsan_memory_order_relaxed = 1 << 0, - __tsan_memory_order_consume = 1 << 1, - __tsan_memory_order_acquire = 1 << 2, - __tsan_memory_order_release = 1 << 3, - __tsan_memory_order_acq_rel = 1 << 4, - __tsan_memory_order_seq_cst = 1 << 5, + __tsan_memory_order_relaxed, + __tsan_memory_order_consume, + __tsan_memory_order_acquire, + __tsan_memory_order_release, + __tsan_memory_order_acq_rel, + __tsan_memory_order_seq_cst } __tsan_memory_order; __tsan_atomic8 __tsan_atomic8_load(const volatile __tsan_atomic8 *a, - __tsan_memory_order mo); + __tsan_memory_order mo) INTERFACE_ATTRIBUTE; __tsan_atomic16 __tsan_atomic16_load(const volatile __tsan_atomic16 *a, - __tsan_memory_order mo); + __tsan_memory_order mo) INTERFACE_ATTRIBUTE; __tsan_atomic32 __tsan_atomic32_load(const volatile __tsan_atomic32 *a, - __tsan_memory_order mo); + __tsan_memory_order mo) INTERFACE_ATTRIBUTE; __tsan_atomic64 __tsan_atomic64_load(const volatile __tsan_atomic64 *a, - __tsan_memory_order mo); + __tsan_memory_order mo) INTERFACE_ATTRIBUTE; +__tsan_atomic128 __tsan_atomic128_load(const volatile __tsan_atomic128 *a, + __tsan_memory_order mo) INTERFACE_ATTRIBUTE; void __tsan_atomic8_store(volatile __tsan_atomic8 *a, __tsan_atomic8 v, - __tsan_memory_order mo); + __tsan_memory_order mo) INTERFACE_ATTRIBUTE; void __tsan_atomic16_store(volatile __tsan_atomic16 *a, __tsan_atomic16 v, - __tsan_memory_order mo); + __tsan_memory_order mo) INTERFACE_ATTRIBUTE; void __tsan_atomic32_store(volatile __tsan_atomic32 *a, __tsan_atomic32 v, - __tsan_memory_order mo); + __tsan_memory_order mo) INTERFACE_ATTRIBUTE; void __tsan_atomic64_store(volatile __tsan_atomic64 *a, __tsan_atomic64 v, - __tsan_memory_order mo); + __tsan_memory_order mo) INTERFACE_ATTRIBUTE; +void __tsan_atomic128_store(volatile __tsan_atomic128 *a, __tsan_atomic128 v, + __tsan_memory_order mo) INTERFACE_ATTRIBUTE; __tsan_atomic8 __tsan_atomic8_exchange(volatile __tsan_atomic8 *a, - __tsan_atomic8 v, __tsan_memory_order mo); + __tsan_atomic8 v, __tsan_memory_order mo) INTERFACE_ATTRIBUTE; __tsan_atomic16 __tsan_atomic16_exchange(volatile __tsan_atomic16 *a, - __tsan_atomic16 v, __tsan_memory_order mo); + __tsan_atomic16 v, __tsan_memory_order mo) INTERFACE_ATTRIBUTE; __tsan_atomic32 __tsan_atomic32_exchange(volatile __tsan_atomic32 *a, - __tsan_atomic32 v, __tsan_memory_order mo); + __tsan_atomic32 v, __tsan_memory_order mo) INTERFACE_ATTRIBUTE; __tsan_atomic64 __tsan_atomic64_exchange(volatile __tsan_atomic64 *a, - __tsan_atomic64 v, __tsan_memory_order mo); + __tsan_atomic64 v, __tsan_memory_order mo) INTERFACE_ATTRIBUTE; +__tsan_atomic128 __tsan_atomic128_exchange(volatile __tsan_atomic128 *a, + __tsan_atomic128 v, __tsan_memory_order mo) INTERFACE_ATTRIBUTE; __tsan_atomic8 __tsan_atomic8_fetch_add(volatile __tsan_atomic8 *a, - __tsan_atomic8 v, __tsan_memory_order mo); + __tsan_atomic8 v, __tsan_memory_order mo) INTERFACE_ATTRIBUTE; __tsan_atomic16 __tsan_atomic16_fetch_add(volatile __tsan_atomic16 *a, - __tsan_atomic16 v, __tsan_memory_order mo); + __tsan_atomic16 v, __tsan_memory_order mo) INTERFACE_ATTRIBUTE; __tsan_atomic32 __tsan_atomic32_fetch_add(volatile __tsan_atomic32 *a, - __tsan_atomic32 v, __tsan_memory_order mo); + __tsan_atomic32 v, __tsan_memory_order mo) INTERFACE_ATTRIBUTE; __tsan_atomic64 __tsan_atomic64_fetch_add(volatile __tsan_atomic64 *a, - __tsan_atomic64 v, __tsan_memory_order mo); + __tsan_atomic64 v, __tsan_memory_order mo) INTERFACE_ATTRIBUTE; +__tsan_atomic128 __tsan_atomic128_fetch_add(volatile __tsan_atomic128 *a, + __tsan_atomic128 v, __tsan_memory_order mo) INTERFACE_ATTRIBUTE; + +__tsan_atomic8 __tsan_atomic8_fetch_sub(volatile __tsan_atomic8 *a, + __tsan_atomic8 v, __tsan_memory_order mo) INTERFACE_ATTRIBUTE; +__tsan_atomic16 __tsan_atomic16_fetch_sub(volatile __tsan_atomic16 *a, + __tsan_atomic16 v, __tsan_memory_order mo) INTERFACE_ATTRIBUTE; +__tsan_atomic32 __tsan_atomic32_fetch_sub(volatile __tsan_atomic32 *a, + __tsan_atomic32 v, __tsan_memory_order mo) INTERFACE_ATTRIBUTE; +__tsan_atomic64 __tsan_atomic64_fetch_sub(volatile __tsan_atomic64 *a, + __tsan_atomic64 v, __tsan_memory_order mo) INTERFACE_ATTRIBUTE; +__tsan_atomic128 __tsan_atomic128_fetch_sub(volatile __tsan_atomic128 *a, + __tsan_atomic128 v, __tsan_memory_order mo) INTERFACE_ATTRIBUTE; __tsan_atomic8 __tsan_atomic8_fetch_and(volatile __tsan_atomic8 *a, - __tsan_atomic8 v, __tsan_memory_order mo); + __tsan_atomic8 v, __tsan_memory_order mo) INTERFACE_ATTRIBUTE; __tsan_atomic16 __tsan_atomic16_fetch_and(volatile __tsan_atomic16 *a, - __tsan_atomic16 v, __tsan_memory_order mo); + __tsan_atomic16 v, __tsan_memory_order mo) INTERFACE_ATTRIBUTE; __tsan_atomic32 __tsan_atomic32_fetch_and(volatile __tsan_atomic32 *a, - __tsan_atomic32 v, __tsan_memory_order mo); + __tsan_atomic32 v, __tsan_memory_order mo) INTERFACE_ATTRIBUTE; __tsan_atomic64 __tsan_atomic64_fetch_and(volatile __tsan_atomic64 *a, - __tsan_atomic64 v, __tsan_memory_order mo); + __tsan_atomic64 v, __tsan_memory_order mo) INTERFACE_ATTRIBUTE; +__tsan_atomic128 __tsan_atomic128_fetch_and(volatile __tsan_atomic128 *a, + __tsan_atomic128 v, __tsan_memory_order mo) INTERFACE_ATTRIBUTE; __tsan_atomic8 __tsan_atomic8_fetch_or(volatile __tsan_atomic8 *a, - __tsan_atomic8 v, __tsan_memory_order mo); + __tsan_atomic8 v, __tsan_memory_order mo) INTERFACE_ATTRIBUTE; __tsan_atomic16 __tsan_atomic16_fetch_or(volatile __tsan_atomic16 *a, - __tsan_atomic16 v, __tsan_memory_order mo); + __tsan_atomic16 v, __tsan_memory_order mo) INTERFACE_ATTRIBUTE; __tsan_atomic32 __tsan_atomic32_fetch_or(volatile __tsan_atomic32 *a, - __tsan_atomic32 v, __tsan_memory_order mo); + __tsan_atomic32 v, __tsan_memory_order mo) INTERFACE_ATTRIBUTE; __tsan_atomic64 __tsan_atomic64_fetch_or(volatile __tsan_atomic64 *a, - __tsan_atomic64 v, __tsan_memory_order mo); + __tsan_atomic64 v, __tsan_memory_order mo) INTERFACE_ATTRIBUTE; +__tsan_atomic128 __tsan_atomic128_fetch_or(volatile __tsan_atomic128 *a, + __tsan_atomic128 v, __tsan_memory_order mo) INTERFACE_ATTRIBUTE; __tsan_atomic8 __tsan_atomic8_fetch_xor(volatile __tsan_atomic8 *a, - __tsan_atomic8 v, __tsan_memory_order mo); + __tsan_atomic8 v, __tsan_memory_order mo) INTERFACE_ATTRIBUTE; __tsan_atomic16 __tsan_atomic16_fetch_xor(volatile __tsan_atomic16 *a, - __tsan_atomic16 v, __tsan_memory_order mo); + __tsan_atomic16 v, __tsan_memory_order mo) INTERFACE_ATTRIBUTE; __tsan_atomic32 __tsan_atomic32_fetch_xor(volatile __tsan_atomic32 *a, - __tsan_atomic32 v, __tsan_memory_order mo); + __tsan_atomic32 v, __tsan_memory_order mo) INTERFACE_ATTRIBUTE; __tsan_atomic64 __tsan_atomic64_fetch_xor(volatile __tsan_atomic64 *a, - __tsan_atomic64 v, __tsan_memory_order mo); + __tsan_atomic64 v, __tsan_memory_order mo) INTERFACE_ATTRIBUTE; +__tsan_atomic128 __tsan_atomic128_fetch_xor(volatile __tsan_atomic128 *a, + __tsan_atomic128 v, __tsan_memory_order mo) INTERFACE_ATTRIBUTE; + +__tsan_atomic8 __tsan_atomic8_fetch_nand(volatile __tsan_atomic8 *a, + __tsan_atomic8 v, __tsan_memory_order mo) INTERFACE_ATTRIBUTE; +__tsan_atomic16 __tsan_atomic16_fetch_nand(volatile __tsan_atomic16 *a, + __tsan_atomic16 v, __tsan_memory_order mo) INTERFACE_ATTRIBUTE; +__tsan_atomic32 __tsan_atomic32_fetch_nand(volatile __tsan_atomic32 *a, + __tsan_atomic32 v, __tsan_memory_order mo) INTERFACE_ATTRIBUTE; +__tsan_atomic64 __tsan_atomic64_fetch_nand(volatile __tsan_atomic64 *a, + __tsan_atomic64 v, __tsan_memory_order mo) INTERFACE_ATTRIBUTE; +__tsan_atomic128 __tsan_atomic128_fetch_nand(volatile __tsan_atomic128 *a, + __tsan_atomic128 v, __tsan_memory_order mo) INTERFACE_ATTRIBUTE; int __tsan_atomic8_compare_exchange_weak(volatile __tsan_atomic8 *a, - __tsan_atomic8 *c, __tsan_atomic8 v, __tsan_memory_order mo); + __tsan_atomic8 *c, __tsan_atomic8 v, __tsan_memory_order mo, + __tsan_memory_order fail_mo) INTERFACE_ATTRIBUTE; int __tsan_atomic16_compare_exchange_weak(volatile __tsan_atomic16 *a, - __tsan_atomic16 *c, __tsan_atomic16 v, __tsan_memory_order mo); + __tsan_atomic16 *c, __tsan_atomic16 v, __tsan_memory_order mo, + __tsan_memory_order fail_mo) INTERFACE_ATTRIBUTE; int __tsan_atomic32_compare_exchange_weak(volatile __tsan_atomic32 *a, - __tsan_atomic32 *c, __tsan_atomic32 v, __tsan_memory_order mo); + __tsan_atomic32 *c, __tsan_atomic32 v, __tsan_memory_order mo, + __tsan_memory_order fail_mo) INTERFACE_ATTRIBUTE; int __tsan_atomic64_compare_exchange_weak(volatile __tsan_atomic64 *a, - __tsan_atomic64 *c, __tsan_atomic64 v, __tsan_memory_order mo); + __tsan_atomic64 *c, __tsan_atomic64 v, __tsan_memory_order mo, + __tsan_memory_order fail_mo) INTERFACE_ATTRIBUTE; +int __tsan_atomic128_compare_exchange_weak(volatile __tsan_atomic128 *a, + __tsan_atomic128 *c, __tsan_atomic128 v, __tsan_memory_order mo, + __tsan_memory_order fail_mo) INTERFACE_ATTRIBUTE; int __tsan_atomic8_compare_exchange_strong(volatile __tsan_atomic8 *a, - __tsan_atomic8 *c, __tsan_atomic8 v, __tsan_memory_order mo); + __tsan_atomic8 *c, __tsan_atomic8 v, __tsan_memory_order mo, + __tsan_memory_order fail_mo) INTERFACE_ATTRIBUTE; int __tsan_atomic16_compare_exchange_strong(volatile __tsan_atomic16 *a, - __tsan_atomic16 *c, __tsan_atomic16 v, __tsan_memory_order mo); + __tsan_atomic16 *c, __tsan_atomic16 v, __tsan_memory_order mo, + __tsan_memory_order fail_mo) INTERFACE_ATTRIBUTE; int __tsan_atomic32_compare_exchange_strong(volatile __tsan_atomic32 *a, - __tsan_atomic32 *c, __tsan_atomic32 v, __tsan_memory_order mo); + __tsan_atomic32 *c, __tsan_atomic32 v, __tsan_memory_order mo, + __tsan_memory_order fail_mo) INTERFACE_ATTRIBUTE; int __tsan_atomic64_compare_exchange_strong(volatile __tsan_atomic64 *a, - __tsan_atomic64 *c, __tsan_atomic64 v, __tsan_memory_order mo); + __tsan_atomic64 *c, __tsan_atomic64 v, __tsan_memory_order mo, + __tsan_memory_order fail_mo) INTERFACE_ATTRIBUTE; +int __tsan_atomic128_compare_exchange_strong(volatile __tsan_atomic128 *a, + __tsan_atomic128 *c, __tsan_atomic128 v, __tsan_memory_order mo, + __tsan_memory_order fail_mo) INTERFACE_ATTRIBUTE; -void __tsan_atomic_thread_fence(__tsan_memory_order mo); +__tsan_atomic8 __tsan_atomic8_compare_exchange_val( + volatile __tsan_atomic8 *a, __tsan_atomic8 c, __tsan_atomic8 v, + __tsan_memory_order mo, __tsan_memory_order fail_mo) INTERFACE_ATTRIBUTE; +__tsan_atomic16 __tsan_atomic16_compare_exchange_val( + volatile __tsan_atomic16 *a, __tsan_atomic16 c, __tsan_atomic16 v, + __tsan_memory_order mo, __tsan_memory_order fail_mo) INTERFACE_ATTRIBUTE; +__tsan_atomic32 __tsan_atomic32_compare_exchange_val( + volatile __tsan_atomic32 *a, __tsan_atomic32 c, __tsan_atomic32 v, + __tsan_memory_order mo, __tsan_memory_order fail_mo) INTERFACE_ATTRIBUTE; +__tsan_atomic64 __tsan_atomic64_compare_exchange_val( + volatile __tsan_atomic64 *a, __tsan_atomic64 c, __tsan_atomic64 v, + __tsan_memory_order mo, __tsan_memory_order fail_mo) INTERFACE_ATTRIBUTE; +__tsan_atomic128 __tsan_atomic128_compare_exchange_val( + volatile __tsan_atomic128 *a, __tsan_atomic128 c, __tsan_atomic128 v, + __tsan_memory_order mo, __tsan_memory_order fail_mo) INTERFACE_ATTRIBUTE; + +void __tsan_atomic_thread_fence(__tsan_memory_order mo) INTERFACE_ATTRIBUTE; +void __tsan_atomic_signal_fence(__tsan_memory_order mo) INTERFACE_ATTRIBUTE; #ifdef __cplusplus } // extern "C" #endif +#undef INTERFACE_ATTRIBUTE + #endif // #ifndef TSAN_INTERFACE_ATOMIC_H diff --git a/lib/tsan/rtl/tsan_interface_inl.h b/lib/tsan/rtl/tsan_interface_inl.h index 233f9028a63b..8a92155d57ef 100644 --- a/lib/tsan/rtl/tsan_interface_inl.h +++ b/lib/tsan/rtl/tsan_interface_inl.h @@ -63,3 +63,11 @@ void __tsan_func_entry(void *pc) { void __tsan_func_exit() { FuncExit(cur_thread()); } + +void __tsan_read_range(void *addr, uptr size) { + MemoryAccessRange(cur_thread(), CALLERPC, (uptr)addr, size, false); +} + +void __tsan_write_range(void *addr, uptr size) { + MemoryAccessRange(cur_thread(), CALLERPC, (uptr)addr, size, true); +} diff --git a/lib/tsan/rtl/tsan_interface_java.cc b/lib/tsan/rtl/tsan_interface_java.cc new file mode 100644 index 000000000000..e425c75800be --- /dev/null +++ b/lib/tsan/rtl/tsan_interface_java.cc @@ -0,0 +1,305 @@ +//===-- tsan_interface_java.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 (TSan), a race detector. +// +//===----------------------------------------------------------------------===// + +#include "tsan_interface_java.h" +#include "tsan_rtl.h" +#include "tsan_mutex.h" +#include "sanitizer_common/sanitizer_internal_defs.h" +#include "sanitizer_common/sanitizer_common.h" +#include "sanitizer_common/sanitizer_placement_new.h" + +using namespace __tsan; // NOLINT + +namespace __tsan { + +const uptr kHeapShadow = 0x300000000000ull; +const uptr kHeapAlignment = 8; + +struct BlockDesc { + bool begin; + Mutex mtx; + SyncVar *head; + + BlockDesc() + : mtx(MutexTypeJavaMBlock, StatMtxJavaMBlock) + , head() { + CHECK_EQ(begin, false); + begin = true; + } + + ~BlockDesc() { + CHECK_EQ(begin, true); + begin = false; + ThreadState *thr = cur_thread(); + SyncVar *s = head; + while (s) { + SyncVar *s1 = s->next; + StatInc(thr, StatSyncDestroyed); + s->mtx.Lock(); + s->mtx.Unlock(); + thr->mset.Remove(s->GetId()); + DestroyAndFree(s); + s = s1; + } + } +}; + +struct JavaContext { + const uptr heap_begin; + const uptr heap_size; + BlockDesc *heap_shadow; + + JavaContext(jptr heap_begin, jptr heap_size) + : heap_begin(heap_begin) + , heap_size(heap_size) { + uptr size = heap_size / kHeapAlignment * sizeof(BlockDesc); + heap_shadow = (BlockDesc*)MmapFixedNoReserve(kHeapShadow, size); + if ((uptr)heap_shadow != kHeapShadow) { + Printf("ThreadSanitizer: failed to mmap Java heap shadow\n"); + Die(); + } + } +}; + +class ScopedJavaFunc { + public: + ScopedJavaFunc(ThreadState *thr, uptr pc) + : thr_(thr) { + Initialize(thr_); + FuncEntry(thr, pc); + CHECK_EQ(thr_->in_rtl, 0); + thr_->in_rtl++; + } + + ~ScopedJavaFunc() { + thr_->in_rtl--; + CHECK_EQ(thr_->in_rtl, 0); + FuncExit(thr_); + // FIXME(dvyukov): process pending signals. + } + + private: + ThreadState *thr_; +}; + +static u64 jctx_buf[sizeof(JavaContext) / sizeof(u64) + 1]; +static JavaContext *jctx; + +static BlockDesc *getblock(uptr addr) { + uptr i = (addr - jctx->heap_begin) / kHeapAlignment; + return &jctx->heap_shadow[i]; +} + +static uptr USED getmem(BlockDesc *b) { + uptr i = b - jctx->heap_shadow; + uptr p = jctx->heap_begin + i * kHeapAlignment; + CHECK_GE(p, jctx->heap_begin); + CHECK_LT(p, jctx->heap_begin + jctx->heap_size); + return p; +} + +static BlockDesc *getblockbegin(uptr addr) { + for (BlockDesc *b = getblock(addr);; b--) { + CHECK_GE(b, jctx->heap_shadow); + if (b->begin) + return b; + } + return 0; +} + +SyncVar* GetJavaSync(ThreadState *thr, uptr pc, uptr addr, + bool write_lock, bool create) { + if (jctx == 0 || addr < jctx->heap_begin + || addr >= jctx->heap_begin + jctx->heap_size) + return 0; + BlockDesc *b = getblockbegin(addr); + DPrintf("#%d: GetJavaSync %p->%p\n", thr->tid, addr, b); + Lock l(&b->mtx); + SyncVar *s = b->head; + for (; s; s = s->next) { + if (s->addr == addr) { + DPrintf("#%d: found existing sync for %p\n", thr->tid, addr); + break; + } + } + if (s == 0 && create) { + DPrintf("#%d: creating new sync for %p\n", thr->tid, addr); + s = CTX()->synctab.Create(thr, pc, addr); + s->next = b->head; + b->head = s; + } + if (s) { + if (write_lock) + s->mtx.Lock(); + else + s->mtx.ReadLock(); + } + return s; +} + +SyncVar* GetAndRemoveJavaSync(ThreadState *thr, uptr pc, uptr addr) { + // We do not destroy Java mutexes other than in __tsan_java_free(). + return 0; +} + +} // namespace __tsan { + +#define SCOPED_JAVA_FUNC(func) \ + ThreadState *thr = cur_thread(); \ + const uptr caller_pc = GET_CALLER_PC(); \ + const uptr pc = (uptr)&func; \ + (void)pc; \ + ScopedJavaFunc scoped(thr, caller_pc); \ +/**/ + +void __tsan_java_init(jptr heap_begin, jptr heap_size) { + SCOPED_JAVA_FUNC(__tsan_java_init); + DPrintf("#%d: java_init(%p, %p)\n", thr->tid, heap_begin, heap_size); + CHECK_EQ(jctx, 0); + CHECK_GT(heap_begin, 0); + CHECK_GT(heap_size, 0); + CHECK_EQ(heap_begin % kHeapAlignment, 0); + CHECK_EQ(heap_size % kHeapAlignment, 0); + CHECK_LT(heap_begin, heap_begin + heap_size); + jctx = new(jctx_buf) JavaContext(heap_begin, heap_size); +} + +int __tsan_java_fini() { + SCOPED_JAVA_FUNC(__tsan_java_fini); + DPrintf("#%d: java_fini()\n", thr->tid); + CHECK_NE(jctx, 0); + // FIXME(dvyukov): this does not call atexit() callbacks. + int status = Finalize(thr); + DPrintf("#%d: java_fini() = %d\n", thr->tid, status); + return status; +} + +void __tsan_java_alloc(jptr ptr, jptr size) { + SCOPED_JAVA_FUNC(__tsan_java_alloc); + DPrintf("#%d: java_alloc(%p, %p)\n", thr->tid, ptr, size); + CHECK_NE(jctx, 0); + CHECK_NE(size, 0); + CHECK_EQ(ptr % kHeapAlignment, 0); + CHECK_EQ(size % kHeapAlignment, 0); + CHECK_GE(ptr, jctx->heap_begin); + CHECK_LE(ptr + size, jctx->heap_begin + jctx->heap_size); + + BlockDesc *b = getblock(ptr); + new(b) BlockDesc(); +} + +void __tsan_java_free(jptr ptr, jptr size) { + SCOPED_JAVA_FUNC(__tsan_java_free); + DPrintf("#%d: java_free(%p, %p)\n", thr->tid, ptr, size); + CHECK_NE(jctx, 0); + CHECK_NE(size, 0); + CHECK_EQ(ptr % kHeapAlignment, 0); + CHECK_EQ(size % kHeapAlignment, 0); + CHECK_GE(ptr, jctx->heap_begin); + CHECK_LE(ptr + size, jctx->heap_begin + jctx->heap_size); + + BlockDesc *beg = getblock(ptr); + BlockDesc *end = getblock(ptr + size); + for (BlockDesc *b = beg; b != end; b++) { + if (b->begin) + b->~BlockDesc(); + } +} + +void __tsan_java_move(jptr src, jptr dst, jptr size) { + SCOPED_JAVA_FUNC(__tsan_java_move); + DPrintf("#%d: java_move(%p, %p, %p)\n", thr->tid, src, dst, size); + CHECK_NE(jctx, 0); + CHECK_NE(size, 0); + CHECK_EQ(src % kHeapAlignment, 0); + CHECK_EQ(dst % kHeapAlignment, 0); + CHECK_EQ(size % kHeapAlignment, 0); + CHECK_GE(src, jctx->heap_begin); + CHECK_LE(src + size, jctx->heap_begin + jctx->heap_size); + CHECK_GE(dst, jctx->heap_begin); + CHECK_LE(dst + size, jctx->heap_begin + jctx->heap_size); + CHECK(dst >= src + size || src >= dst + size); + + // Assuming it's not running concurrently with threads that do + // memory accesses and mutex operations (stop-the-world phase). + { // NOLINT + BlockDesc *s = getblock(src); + BlockDesc *d = getblock(dst); + BlockDesc *send = getblock(src + size); + for (; s != send; s++, d++) { + CHECK_EQ(d->begin, false); + if (s->begin) { + DPrintf("#%d: moving block %p->%p\n", thr->tid, getmem(s), getmem(d)); + new(d) BlockDesc; + d->head = s->head; + for (SyncVar *sync = d->head; sync; sync = sync->next) { + uptr newaddr = sync->addr - src + dst; + DPrintf("#%d: moving sync %p->%p\n", thr->tid, sync->addr, newaddr); + sync->addr = newaddr; + } + s->head = 0; + s->~BlockDesc(); + } + } + } + + { // NOLINT + u64 *s = (u64*)MemToShadow(src); + u64 *d = (u64*)MemToShadow(dst); + u64 *send = (u64*)MemToShadow(src + size); + for (; s != send; s++, d++) { + *d = *s; + *s = 0; + } + } +} + +void __tsan_java_mutex_lock(jptr addr) { + SCOPED_JAVA_FUNC(__tsan_java_mutex_lock); + DPrintf("#%d: java_mutex_lock(%p)\n", thr->tid, addr); + CHECK_NE(jctx, 0); + CHECK_GE(addr, jctx->heap_begin); + CHECK_LT(addr, jctx->heap_begin + jctx->heap_size); + + MutexLock(thr, pc, addr); +} + +void __tsan_java_mutex_unlock(jptr addr) { + SCOPED_JAVA_FUNC(__tsan_java_mutex_unlock); + DPrintf("#%d: java_mutex_unlock(%p)\n", thr->tid, addr); + CHECK_NE(jctx, 0); + CHECK_GE(addr, jctx->heap_begin); + CHECK_LT(addr, jctx->heap_begin + jctx->heap_size); + + MutexUnlock(thr, pc, addr); +} + +void __tsan_java_mutex_read_lock(jptr addr) { + SCOPED_JAVA_FUNC(__tsan_java_mutex_read_lock); + DPrintf("#%d: java_mutex_read_lock(%p)\n", thr->tid, addr); + CHECK_NE(jctx, 0); + CHECK_GE(addr, jctx->heap_begin); + CHECK_LT(addr, jctx->heap_begin + jctx->heap_size); + + MutexReadLock(thr, pc, addr); +} + +void __tsan_java_mutex_read_unlock(jptr addr) { + SCOPED_JAVA_FUNC(__tsan_java_mutex_read_unlock); + DPrintf("#%d: java_mutex_read_unlock(%p)\n", thr->tid, addr); + CHECK_NE(jctx, 0); + CHECK_GE(addr, jctx->heap_begin); + CHECK_LT(addr, jctx->heap_begin + jctx->heap_size); + + MutexReadUnlock(thr, pc, addr); +} diff --git a/lib/tsan/rtl/tsan_interface_java.h b/lib/tsan/rtl/tsan_interface_java.h new file mode 100644 index 000000000000..241483aaa015 --- /dev/null +++ b/lib/tsan/rtl/tsan_interface_java.h @@ -0,0 +1,74 @@ +//===-- tsan_interface_java.h -----------------------------------*- C++ -*-===// +// +// 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 (TSan), a race detector. +// +// Interface for verification of Java or mixed Java/C++ programs. +// The interface is intended to be used from within a JVM and notify TSan +// about such events like Java locks and GC memory compaction. +// +// For plain memory accesses and function entry/exit a JVM is intended to use +// C++ interfaces: __tsan_readN/writeN and __tsan_func_enter/exit. +// +// For volatile memory accesses and atomic operations JVM is intended to use +// standard atomics API: __tsan_atomicN_load/store/etc. +// +// For usage examples see lit_tests/java_*.cc +//===----------------------------------------------------------------------===// +#ifndef TSAN_INTERFACE_JAVA_H +#define TSAN_INTERFACE_JAVA_H + +#ifndef INTERFACE_ATTRIBUTE +# define INTERFACE_ATTRIBUTE __attribute__((visibility("default"))) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef unsigned long jptr; // NOLINT + +// Must be called before any other callback from Java. +void __tsan_java_init(jptr heap_begin, jptr heap_size) INTERFACE_ATTRIBUTE; +// Must be called when the application exits. +// Not necessary the last callback (concurrently running threads are OK). +// Returns exit status or 0 if tsan does not want to override it. +int __tsan_java_fini() INTERFACE_ATTRIBUTE; + +// Callback for memory allocations. +// May be omitted for allocations that are not subject to data races +// nor contain synchronization objects (e.g. String). +void __tsan_java_alloc(jptr ptr, jptr size) INTERFACE_ATTRIBUTE; +// Callback for memory free. +// Can be aggregated for several objects (preferably). +void __tsan_java_free(jptr ptr, jptr size) INTERFACE_ATTRIBUTE; +// Callback for memory move by GC. +// Can be aggregated for several objects (preferably). +// The ranges must not overlap. +void __tsan_java_move(jptr src, jptr dst, jptr size) INTERFACE_ATTRIBUTE; + +// Mutex lock. +// Addr is any unique address associated with the mutex. +// Must not be called on recursive reentry. +// Object.wait() is handled as a pair of unlock/lock. +void __tsan_java_mutex_lock(jptr addr) INTERFACE_ATTRIBUTE; +// Mutex unlock. +void __tsan_java_mutex_unlock(jptr addr) INTERFACE_ATTRIBUTE; +// Mutex read lock. +void __tsan_java_mutex_read_lock(jptr addr) INTERFACE_ATTRIBUTE; +// Mutex read unlock. +void __tsan_java_mutex_read_unlock(jptr addr) INTERFACE_ATTRIBUTE; + +#ifdef __cplusplus +} // extern "C" +#endif + +#undef INTERFACE_ATTRIBUTE + +#endif // #ifndef TSAN_INTERFACE_JAVA_H diff --git a/lib/tsan/rtl/tsan_mman.cc b/lib/tsan/rtl/tsan_mman.cc index 7f956dfe26bf..82f7105d60db 100644 --- a/lib/tsan/rtl/tsan_mman.cc +++ b/lib/tsan/rtl/tsan_mman.cc @@ -11,34 +11,63 @@ // //===----------------------------------------------------------------------===// #include "sanitizer_common/sanitizer_common.h" +#include "sanitizer_common/sanitizer_placement_new.h" #include "tsan_mman.h" #include "tsan_rtl.h" #include "tsan_report.h" #include "tsan_flags.h" +// May be overriden by front-end. +extern "C" void WEAK __tsan_malloc_hook(void *ptr, uptr size) { + (void)ptr; + (void)size; +} + +extern "C" void WEAK __tsan_free_hook(void *ptr) { + (void)ptr; +} + namespace __tsan { +static char allocator_placeholder[sizeof(Allocator)] ALIGNED(64); +Allocator *allocator() { + return reinterpret_cast<Allocator*>(&allocator_placeholder); +} + +void InitializeAllocator() { + allocator()->Init(); +} + +void AlloctorThreadFinish(ThreadState *thr) { + allocator()->SwallowCache(&thr->alloc_cache); +} + static void SignalUnsafeCall(ThreadState *thr, uptr pc) { if (!thr->in_signal_handler || !flags()->report_signal_unsafe) return; + Context *ctx = CTX(); StackTrace stack; stack.ObtainCurrent(thr, pc); + Lock l(&ctx->thread_mtx); ScopedReport rep(ReportTypeSignalUnsafe); - rep.AddStack(&stack); - OutputReport(rep, rep.GetReport()->stacks[0]); + if (!IsFiredSuppression(ctx, rep, stack)) { + rep.AddStack(&stack); + OutputReport(ctx, rep, rep.GetReport()->stacks[0]); + } } -void *user_alloc(ThreadState *thr, uptr pc, uptr sz) { +void *user_alloc(ThreadState *thr, uptr pc, uptr sz, uptr align) { CHECK_GT(thr->in_rtl, 0); - if (sz + sizeof(MBlock) < sz) - return 0; - MBlock *b = (MBlock*)InternalAlloc(sz + sizeof(MBlock)); - if (b == 0) + void *p = allocator()->Allocate(&thr->alloc_cache, sz, align); + if (p == 0) return 0; + MBlock *b = new(allocator()->GetMetaData(p)) MBlock; b->size = sz; - void *p = b + 1; + b->head = 0; + b->alloc_tid = thr->unique_id; + b->alloc_stack_id = CurrentStackId(thr, pc); if (CTX() && CTX()->initialized) { - MemoryResetRange(thr, pc, (uptr)p, sz); + MemoryRangeImitateWrite(thr, pc, (uptr)p, sz); } DPrintf("#%d: alloc(%zu) = %p\n", thr->tid, sz, p); SignalUnsafeCall(thr, pc); @@ -49,12 +78,24 @@ void user_free(ThreadState *thr, uptr pc, void *p) { CHECK_GT(thr->in_rtl, 0); CHECK_NE(p, (void*)0); DPrintf("#%d: free(%p)\n", thr->tid, p); - MBlock *b = user_mblock(thr, p); - p = b + 1; + MBlock *b = (MBlock*)allocator()->GetMetaData(p); + if (b->head) { + Lock l(&b->mtx); + for (SyncVar *s = b->head; s;) { + SyncVar *res = s; + s = s->next; + StatInc(thr, StatSyncDestroyed); + res->mtx.Lock(); + res->mtx.Unlock(); + DestroyAndFree(res); + } + b->head = 0; + } if (CTX() && CTX()->initialized && thr->in_rtl == 1) { MemoryRangeFreed(thr, pc, (uptr)p, b->size); } - InternalFree(b); + b->~MBlock(); + allocator()->Deallocate(&thr->alloc_cache, p); SignalUnsafeCall(thr, pc); } @@ -78,26 +119,28 @@ void *user_realloc(ThreadState *thr, uptr pc, void *p, uptr sz) { return p2; } -void *user_alloc_aligned(ThreadState *thr, uptr pc, uptr sz, uptr align) { - CHECK_GT(thr->in_rtl, 0); - void *p = user_alloc(thr, pc, sz + align); - void *pa = RoundUp(p, align); - DCHECK_LE((uptr)pa + sz, (uptr)p + sz + align); - return pa; -} - MBlock *user_mblock(ThreadState *thr, void *p) { - CHECK_GT(thr->in_rtl, 0); CHECK_NE(p, (void*)0); - MBlock *b = (MBlock*)InternalAllocBlock(p); - // FIXME: Output a warning, it's a user error. - if (p < (char*)(b + 1) || p > (char*)(b + 1) + b->size) { - TsanPrintf("user_mblock p=%p b=%p size=%zu beg=%p end=%p\n", - p, b, b->size, (char*)(b + 1), (char*)(b + 1) + b->size); - CHECK_GE(p, (char*)(b + 1)); - CHECK_LE(p, (char*)(b + 1) + b->size); - } - return b; + Allocator *a = allocator(); + void *b = a->GetBlockBegin(p); + CHECK_NE(b, 0); + return (MBlock*)a->GetMetaData(b); +} + +void invoke_malloc_hook(void *ptr, uptr size) { + Context *ctx = CTX(); + ThreadState *thr = cur_thread(); + if (ctx == 0 || !ctx->initialized || thr->in_rtl) + return; + __tsan_malloc_hook(ptr, size); +} + +void invoke_free_hook(void *ptr) { + Context *ctx = CTX(); + ThreadState *thr = cur_thread(); + if (ctx == 0 || !ctx->initialized || thr->in_rtl) + return; + __tsan_free_hook(ptr); } void *internal_alloc(MBlockType typ, uptr sz) { diff --git a/lib/tsan/rtl/tsan_mman.h b/lib/tsan/rtl/tsan_mman.h index 53f147e40cea..5cf00eac8d03 100644 --- a/lib/tsan/rtl/tsan_mman.h +++ b/lib/tsan/rtl/tsan_mman.h @@ -17,13 +17,14 @@ namespace __tsan { -// Descriptor of user's memory block. -struct MBlock { - uptr size; -}; +const uptr kDefaultAlignment = 16; + +void InitializeAllocator(); +void AlloctorThreadFinish(ThreadState *thr); // For user allocations. -void *user_alloc(ThreadState *thr, uptr pc, uptr sz); +void *user_alloc(ThreadState *thr, uptr pc, uptr sz, + uptr align = kDefaultAlignment); // Does not accept NULL. void user_free(ThreadState *thr, uptr pc, void *p); void *user_realloc(ThreadState *thr, uptr pc, void *p, uptr sz); @@ -32,6 +33,10 @@ void *user_alloc_aligned(ThreadState *thr, uptr pc, uptr sz, uptr align); // returns the descriptor of the block. MBlock *user_mblock(ThreadState *thr, void *p); +// Invoking malloc/free hooks that may be installed by the user. +void invoke_malloc_hook(void *ptr, uptr size); +void invoke_free_hook(void *ptr); + enum MBlockType { MBlockScopedBuf, MBlockString, @@ -54,9 +59,10 @@ enum MBlockType { MBlockSuppression, MBlockExpectRace, MBlockSignal, + MBlockFD, // This must be the last. - MBlockTypeCount, + MBlockTypeCount }; // For internal data structures. @@ -70,45 +76,5 @@ void DestroyAndFree(T *&p) { p = 0; } -template<typename T> -class InternalScopedBuf { - public: - explicit InternalScopedBuf(uptr cnt) { - cnt_ = cnt; - ptr_ = (T*)internal_alloc(MBlockScopedBuf, cnt * sizeof(T)); - } - - ~InternalScopedBuf() { - internal_free(ptr_); - } - - operator T *() { - return ptr_; - } - - T &operator[](uptr i) { - return ptr_[i]; - } - - T *Ptr() { - return ptr_; - } - - uptr Count() { - return cnt_; - } - - uptr Size() { - return cnt_ * sizeof(T); - } - - private: - T *ptr_; - uptr cnt_; - - InternalScopedBuf(const InternalScopedBuf&); - void operator = (const InternalScopedBuf&); -}; - } // namespace __tsan #endif // TSAN_MMAN_H diff --git a/lib/tsan/rtl/tsan_mutex.cc b/lib/tsan/rtl/tsan_mutex.cc index 1a70f8fe4430..335ca2211d13 100644 --- a/lib/tsan/rtl/tsan_mutex.cc +++ b/lib/tsan/rtl/tsan_mutex.cc @@ -25,22 +25,28 @@ namespace __tsan { // then Report mutex can be locked while under Threads mutex. // The leaf mutexes can be locked under any other mutexes. // Recursive locking is not supported. +#if TSAN_DEBUG && !TSAN_GO const MutexType MutexTypeLeaf = (MutexType)-1; static MutexType CanLockTab[MutexTypeCount][MutexTypeCount] = { - /*0 MutexTypeInvalid*/ {}, - /*1 MutexTypeTrace*/ {MutexTypeLeaf}, - /*2 MutexTypeThreads*/ {MutexTypeReport}, - /*3 MutexTypeReport*/ {}, - /*4 MutexTypeSyncVar*/ {}, - /*5 MutexTypeSyncTab*/ {MutexTypeSyncVar}, - /*6 MutexTypeSlab*/ {MutexTypeLeaf}, - /*7 MutexTypeAnnotations*/ {}, - /*8 MutexTypeAtExit*/ {MutexTypeSyncTab}, + /*0 MutexTypeInvalid*/ {}, + /*1 MutexTypeTrace*/ {MutexTypeLeaf}, + /*2 MutexTypeThreads*/ {MutexTypeReport}, + /*3 MutexTypeReport*/ {MutexTypeSyncTab, MutexTypeMBlock, + MutexTypeJavaMBlock}, + /*4 MutexTypeSyncVar*/ {}, + /*5 MutexTypeSyncTab*/ {MutexTypeSyncVar}, + /*6 MutexTypeSlab*/ {MutexTypeLeaf}, + /*7 MutexTypeAnnotations*/ {}, + /*8 MutexTypeAtExit*/ {MutexTypeSyncTab}, + /*9 MutexTypeMBlock*/ {MutexTypeSyncVar}, + /*10 MutexTypeJavaMBlock*/ {MutexTypeSyncVar}, }; static bool CanLockAdj[MutexTypeCount][MutexTypeCount]; +#endif void InitializeMutex() { +#if TSAN_DEBUG && !TSAN_GO // Build the "can lock" adjacency matrix. // If [i][j]==true, then one can lock mutex j while under mutex i. const int N = MutexTypeCount; @@ -48,7 +54,7 @@ void InitializeMutex() { bool leaf[N] = {}; for (int i = 1; i < N; i++) { for (int j = 0; j < N; j++) { - int z = CanLockTab[i][j]; + MutexType z = CanLockTab[i][j]; if (z == MutexTypeInvalid) continue; if (z == MutexTypeLeaf) { @@ -56,8 +62,8 @@ void InitializeMutex() { leaf[i] = true; continue; } - CHECK(!CanLockAdj[i][z]); - CanLockAdj[i][z] = true; + CHECK(!CanLockAdj[i][(int)z]); + CanLockAdj[i][(int)z] = true; cnt[i]++; } } @@ -92,36 +98,40 @@ void InitializeMutex() { } } #if 0 - TsanPrintf("Can lock graph:\n"); + Printf("Can lock graph:\n"); for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { - TsanPrintf("%d ", CanLockAdj[i][j]); + Printf("%d ", CanLockAdj[i][j]); } - TsanPrintf("\n"); + Printf("\n"); } - TsanPrintf("Can lock graph closure:\n"); + Printf("Can lock graph closure:\n"); for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { - TsanPrintf("%d ", CanLockAdj2[i][j]); + Printf("%d ", CanLockAdj2[i][j]); } - TsanPrintf("\n"); + Printf("\n"); } #endif // Verify that the graph is acyclic. for (int i = 0; i < N; i++) { if (CanLockAdj2[i][i]) { - TsanPrintf("Mutex %d participates in a cycle\n", i); + Printf("Mutex %d participates in a cycle\n", i); Die(); } } +#endif } DeadlockDetector::DeadlockDetector() { // Rely on zero initialization because some mutexes can be locked before ctor. } +#if TSAN_DEBUG && !TSAN_GO void DeadlockDetector::Lock(MutexType t) { - // TsanPrintf("LOCK %d @%zu\n", t, seq_ + 1); + // Printf("LOCK %d @%zu\n", t, seq_ + 1); + CHECK_GT(t, MutexTypeInvalid); + CHECK_LT(t, MutexTypeCount); u64 max_seq = 0; u64 max_idx = MutexTypeInvalid; for (int i = 0; i != MutexTypeCount; i++) { @@ -136,20 +146,21 @@ void DeadlockDetector::Lock(MutexType t) { locked_[t] = ++seq_; if (max_idx == MutexTypeInvalid) return; - // TsanPrintf(" last %d @%zu\n", max_idx, max_seq); + // Printf(" last %d @%zu\n", max_idx, max_seq); if (!CanLockAdj[max_idx][t]) { - TsanPrintf("ThreadSanitizer: internal deadlock detected\n"); - TsanPrintf("ThreadSanitizer: can't lock %d while under %zu\n", + Printf("ThreadSanitizer: internal deadlock detected\n"); + Printf("ThreadSanitizer: can't lock %d while under %zu\n", t, (uptr)max_idx); - Die(); + CHECK(0); } } void DeadlockDetector::Unlock(MutexType t) { - // TsanPrintf("UNLO %d @%zu #%zu\n", t, seq_, locked_[t]); + // Printf("UNLO %d @%zu #%zu\n", t, seq_, locked_[t]); CHECK(locked_[t]); locked_[t] = 0; } +#endif const uptr kUnlocked = 0; const uptr kWriteLock = 1; @@ -256,4 +267,8 @@ void Mutex::ReadUnlock() { #endif } +void Mutex::CheckLocked() { + CHECK_NE(atomic_load(&state_, memory_order_relaxed), 0); +} + } // namespace __tsan diff --git a/lib/tsan/rtl/tsan_mutex.h b/lib/tsan/rtl/tsan_mutex.h index 5b22a4145185..a2b489107a98 100644 --- a/lib/tsan/rtl/tsan_mutex.h +++ b/lib/tsan/rtl/tsan_mutex.h @@ -29,9 +29,11 @@ enum MutexType { MutexTypeSlab, MutexTypeAnnotations, MutexTypeAtExit, + MutexTypeMBlock, + MutexTypeJavaMBlock, // This must be the last. - MutexTypeCount, + MutexTypeCount }; class Mutex { @@ -45,6 +47,8 @@ class Mutex { void ReadLock(); void ReadUnlock(); + void CheckLocked(); + private: atomic_uintptr_t state_; #if TSAN_DEBUG diff --git a/lib/tsan/rtl/tsan_mutexset.cc b/lib/tsan/rtl/tsan_mutexset.cc new file mode 100644 index 000000000000..21587770f687 --- /dev/null +++ b/lib/tsan/rtl/tsan_mutexset.cc @@ -0,0 +1,89 @@ +//===-- tsan_mutexset.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 (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "tsan_mutexset.h" +#include "tsan_rtl.h" + +namespace __tsan { + +const uptr MutexSet::kMaxSize; + +MutexSet::MutexSet() { + size_ = 0; + internal_memset(&descs_, 0, sizeof(descs_)); +} + +void MutexSet::Add(u64 id, bool write, u64 epoch) { + // Look up existing mutex with the same id. + for (uptr i = 0; i < size_; i++) { + if (descs_[i].id == id) { + descs_[i].count++; + descs_[i].epoch = epoch; + return; + } + } + // On overflow, find the oldest mutex and drop it. + if (size_ == kMaxSize) { + u64 minepoch = (u64)-1; + u64 mini = (u64)-1; + for (uptr i = 0; i < size_; i++) { + if (descs_[i].epoch < minepoch) { + minepoch = descs_[i].epoch; + mini = i; + } + } + RemovePos(mini); + CHECK_EQ(size_, kMaxSize - 1); + } + // Add new mutex descriptor. + descs_[size_].id = id; + descs_[size_].write = write; + descs_[size_].epoch = epoch; + descs_[size_].count = 1; + size_++; +} + +void MutexSet::Del(u64 id, bool write) { + for (uptr i = 0; i < size_; i++) { + if (descs_[i].id == id) { + if (--descs_[i].count == 0) + RemovePos(i); + return; + } + } +} + +void MutexSet::Remove(u64 id) { + for (uptr i = 0; i < size_; i++) { + if (descs_[i].id == id) { + RemovePos(i); + return; + } + } +} + +void MutexSet::RemovePos(uptr i) { + CHECK_LT(i, size_); + descs_[i] = descs_[size_ - 1]; + size_--; +} + +uptr MutexSet::Size() const { + return size_; +} + +MutexSet::Desc MutexSet::Get(uptr i) const { + CHECK_LT(i, size_); + return descs_[i]; +} + +} // namespace __tsan diff --git a/lib/tsan/rtl/tsan_mutexset.h b/lib/tsan/rtl/tsan_mutexset.h new file mode 100644 index 000000000000..09223ff6cc48 --- /dev/null +++ b/lib/tsan/rtl/tsan_mutexset.h @@ -0,0 +1,65 @@ +//===-- tsan_mutexset.h -----------------------------------------*- C++ -*-===// +// +// 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 (TSan), a race detector. +// +// MutexSet holds the set of mutexes currently held by a thread. +//===----------------------------------------------------------------------===// +#ifndef TSAN_MUTEXSET_H +#define TSAN_MUTEXSET_H + +#include "tsan_defs.h" + +namespace __tsan { + +class MutexSet { + public: + // Holds limited number of mutexes. + // The oldest mutexes are discarded on overflow. + static const uptr kMaxSize = 64; + struct Desc { + u64 id; + u64 epoch; + int count; + bool write; + }; + + MutexSet(); + // The 'id' is obtained from SyncVar::GetId(). + void Add(u64 id, bool write, u64 epoch); + void Del(u64 id, bool write); + void Remove(u64 id); // Removes the mutex completely (if it's destroyed). + uptr Size() const; + Desc Get(uptr i) const; + + private: +#ifndef TSAN_GO + uptr size_; + Desc descs_[kMaxSize]; +#endif + + void RemovePos(uptr i); +}; + +// Go does not have mutexes, so do not spend memory and time. +// (Go sync.Mutex is actually a semaphore -- can be unlocked +// in different goroutine). +#ifdef TSAN_GO +MutexSet::MutexSet() {} +void MutexSet::Add(u64 id, bool write, u64 epoch) {} +void MutexSet::Del(u64 id, bool write) {} +void MutexSet::Remove(u64 id) {} +void MutexSet::RemovePos(uptr i) {} +uptr MutexSet::Size() const { return 0; } +MutexSet::Desc MutexSet::Get(uptr i) const { return Desc(); } +#endif + +} // namespace __tsan + +#endif // TSAN_REPORT_H diff --git a/lib/tsan/rtl/tsan_platform.h b/lib/tsan/rtl/tsan_platform.h index b557fa1caec5..c859c3e85b19 100644 --- a/lib/tsan/rtl/tsan_platform.h +++ b/lib/tsan/rtl/tsan_platform.h @@ -12,31 +12,85 @@ // Platform-specific code. //===----------------------------------------------------------------------===// +/* +C++ linux memory layout: +0000 0000 0000 - 03c0 0000 0000: protected +03c0 0000 0000 - 1000 0000 0000: shadow +1000 0000 0000 - 6000 0000 0000: protected +6000 0000 0000 - 6200 0000 0000: traces +6200 0000 0000 - 7d00 0000 0000: - +7d00 0000 0000 - 7e00 0000 0000: heap +7e00 0000 0000 - 7fff ffff ffff: modules and main thread stack + +C++ COMPAT linux memory layout: +0000 0000 0000 - 0400 0000 0000: protected +0400 0000 0000 - 1000 0000 0000: shadow +1000 0000 0000 - 2900 0000 0000: protected +2900 0000 0000 - 2c00 0000 0000: modules +2c00 0000 0000 - 6000 0000 0000: - +6000 0000 0000 - 6200 0000 0000: traces +6200 0000 0000 - 7d00 0000 0000: - +7d00 0000 0000 - 7e00 0000 0000: heap +7e00 0000 0000 - 7f00 0000 0000: - +7f00 0000 0000 - 7fff ffff ffff: main thread stack + +Go linux and darwin memory layout: +0000 0000 0000 - 0000 1000 0000: executable +0000 1000 0000 - 00f8 0000 0000: - +00f8 0000 0000 - 0118 0000 0000: heap +0118 0000 0000 - 1000 0000 0000: - +1000 0000 0000 - 1460 0000 0000: shadow +1460 0000 0000 - 6000 0000 0000: - +6000 0000 0000 - 6200 0000 0000: traces +6200 0000 0000 - 7fff ffff ffff: - + +Go windows memory layout: +0000 0000 0000 - 0000 1000 0000: executable +0000 1000 0000 - 00f8 0000 0000: - +00f8 0000 0000 - 0118 0000 0000: heap +0118 0000 0000 - 0100 0000 0000: - +0100 0000 0000 - 0560 0000 0000: shadow +0560 0000 0000 - 0760 0000 0000: traces +0760 0000 0000 - 07ff ffff ffff: - +*/ + #ifndef TSAN_PLATFORM_H #define TSAN_PLATFORM_H -#include "tsan_rtl.h" +#include "tsan_defs.h" +#include "tsan_trace.h" -#if __LP64__ +#if defined(__LP64__) || defined(_WIN64) namespace __tsan { #if defined(TSAN_GO) static const uptr kLinuxAppMemBeg = 0x000000000000ULL; static const uptr kLinuxAppMemEnd = 0x00fcffffffffULL; +# if defined(_WIN32) +static const uptr kLinuxShadowMsk = 0x010000000000ULL; +# else static const uptr kLinuxShadowMsk = 0x100000000000ULL; +# endif // TSAN_COMPAT_SHADOW is intended for COMPAT virtual memory layout, // when memory addresses are of the 0x2axxxxxxxxxx form. // The option is enabled with 'setarch x86_64 -L'. #elif defined(TSAN_COMPAT_SHADOW) && TSAN_COMPAT_SHADOW -static const uptr kLinuxAppMemBeg = 0x2a0000000000ULL; +static const uptr kLinuxAppMemBeg = 0x290000000000ULL; static const uptr kLinuxAppMemEnd = 0x7fffffffffffULL; #else -static const uptr kLinuxAppMemBeg = 0x7ef000000000ULL; +static const uptr kLinuxAppMemBeg = 0x7cf000000000ULL; static const uptr kLinuxAppMemEnd = 0x7fffffffffffULL; #endif static const uptr kLinuxAppMemMsk = 0x7c0000000000ULL; +#if defined(_WIN32) +const uptr kTraceMemBegin = 0x056000000000ULL; +#else +const uptr kTraceMemBegin = 0x600000000000ULL; +#endif +const uptr kTraceMemSize = 0x020000000000ULL; + // This has to be a macro to allow constant initialization of constants below. #ifndef TSAN_GO #define MemToShadow(addr) \ @@ -48,7 +102,7 @@ static const uptr kLinuxAppMemMsk = 0x7c0000000000ULL; static const uptr kLinuxShadowBeg = MemToShadow(kLinuxAppMemBeg); static const uptr kLinuxShadowEnd = - MemToShadow(kLinuxAppMemEnd) | (kPageSize - 1); + MemToShadow(kLinuxAppMemEnd) | 0xff; static inline bool IsAppMem(uptr mem) { return mem >= kLinuxAppMemBeg && mem <= kLinuxAppMemEnd; @@ -62,9 +116,6 @@ static inline uptr ShadowToMem(uptr shadow) { CHECK(IsShadowMem(shadow)); #ifdef TSAN_GO return (shadow & ~kLinuxShadowMsk) / kShadowCnt; -#elif defined(TSAN_COMPAT_SHADOW) && TSAN_COMPAT_SHADOW - // COMPAT mapping is not quite one-to-one. - return (shadow / kShadowCnt) | 0x280000000000ULL; #else return (shadow / kShadowCnt) | kLinuxAppMemMsk; #endif @@ -72,9 +123,10 @@ static inline uptr ShadowToMem(uptr shadow) { // For COMPAT mapping returns an alternative address // that mapped to the same shadow address. +// COMPAT mapping is not quite one-to-one. static inline uptr AlternativeAddress(uptr addr) { #if defined(TSAN_COMPAT_SHADOW) && TSAN_COMPAT_SHADOW - return addr | kLinuxAppMemMsk; + return (addr & ~kLinuxAppMemMsk) | 0x280000000000ULL; #else return 0; #endif @@ -85,16 +137,24 @@ void FlushShadowMemory(); const char *InitializePlatform(); void FinalizePlatform(); +uptr ALWAYS_INLINE INLINE GetThreadTrace(int tid) { + uptr p = kTraceMemBegin + (uptr)tid * kTraceSize * sizeof(Event); + DCHECK_LT(p, kTraceMemBegin + kTraceMemSize); + return p; +} void internal_start_thread(void(*func)(void*), void *arg); +// Says whether the addr relates to a global var. +// Guesses with high probability, may yield both false positives and negatives. +bool IsGlobalVar(uptr addr); uptr GetTlsSize(); void GetThreadStackAndTls(bool main, uptr *stk_addr, uptr *stk_size, uptr *tls_addr, uptr *tls_size); } // namespace __tsan -#else // __LP64__ +#else // defined(__LP64__) || defined(_WIN64) # error "Only 64-bit is supported" #endif diff --git a/lib/tsan/rtl/tsan_platform_linux.cc b/lib/tsan/rtl/tsan_platform_linux.cc index c791c96c14ac..6cc424975125 100644 --- a/lib/tsan/rtl/tsan_platform_linux.cc +++ b/lib/tsan/rtl/tsan_platform_linux.cc @@ -43,14 +43,6 @@ extern "C" int arch_prctl(int code, __sanitizer::uptr *addr); -namespace __sanitizer { - -void Die() { - _exit(1); -} - -} // namespace __sanitizer - namespace __tsan { #ifndef TSAN_GO @@ -79,9 +71,7 @@ uptr GetShadowMemoryConsumption() { } void FlushShadowMemory() { - madvise((void*)kLinuxShadowBeg, - kLinuxShadowEnd - kLinuxShadowBeg, - MADV_DONTNEED); + FlushUnneededShadowMemory(kLinuxShadowBeg, kLinuxShadowEnd - kLinuxShadowBeg); } #ifndef TSAN_GO @@ -91,65 +81,88 @@ static void ProtectRange(uptr beg, uptr end) { if (beg == end) return; if (beg != (uptr)Mprotect(beg, end - beg)) { - TsanPrintf("FATAL: ThreadSanitizer can not protect [%zx,%zx]\n", beg, end); - TsanPrintf("FATAL: Make sure you are not using unlimited stack\n"); + Printf("FATAL: ThreadSanitizer can not protect [%zx,%zx]\n", beg, end); + Printf("FATAL: Make sure you are not using unlimited stack\n"); Die(); } } #endif +#ifndef TSAN_GO void InitializeShadowMemory() { uptr shadow = (uptr)MmapFixedNoReserve(kLinuxShadowBeg, kLinuxShadowEnd - kLinuxShadowBeg); if (shadow != kLinuxShadowBeg) { - TsanPrintf("FATAL: ThreadSanitizer can not mmap the shadow memory\n"); - TsanPrintf("FATAL: Make sure to compile with -fPIE and " - "to link with -pie.\n"); + Printf("FATAL: ThreadSanitizer can not mmap the shadow memory\n"); + Printf("FATAL: Make sure to compile with -fPIE and " + "to link with -pie (%p, %p).\n", shadow, kLinuxShadowBeg); Die(); } -#ifndef TSAN_GO const uptr kClosedLowBeg = 0x200000; const uptr kClosedLowEnd = kLinuxShadowBeg - 1; const uptr kClosedMidBeg = kLinuxShadowEnd + 1; - const uptr kClosedMidEnd = kLinuxAppMemBeg - 1; + const uptr kClosedMidEnd = min(kLinuxAppMemBeg, kTraceMemBegin); ProtectRange(kClosedLowBeg, kClosedLowEnd); ProtectRange(kClosedMidBeg, kClosedMidEnd); -#endif -#ifndef TSAN_GO DPrintf("kClosedLow %zx-%zx (%zuGB)\n", kClosedLowBeg, kClosedLowEnd, (kClosedLowEnd - kClosedLowBeg) >> 30); -#endif DPrintf("kLinuxShadow %zx-%zx (%zuGB)\n", kLinuxShadowBeg, kLinuxShadowEnd, (kLinuxShadowEnd - kLinuxShadowBeg) >> 30); -#ifndef TSAN_GO DPrintf("kClosedMid %zx-%zx (%zuGB)\n", kClosedMidBeg, kClosedMidEnd, (kClosedMidEnd - kClosedMidBeg) >> 30); -#endif DPrintf("kLinuxAppMem %zx-%zx (%zuGB)\n", kLinuxAppMemBeg, kLinuxAppMemEnd, (kLinuxAppMemEnd - kLinuxAppMemBeg) >> 30); DPrintf("stack %zx\n", (uptr)&shadow); } +#endif + +static uptr g_data_start; +static uptr g_data_end; #ifndef TSAN_GO static void CheckPIE() { // Ensure that the binary is indeed compiled with -pie. - ProcessMaps proc_maps; + MemoryMappingLayout proc_maps; uptr start, end; if (proc_maps.Next(&start, &end, /*offset*/0, /*filename*/0, /*filename_size*/0)) { if ((u64)start < kLinuxAppMemBeg) { - TsanPrintf("FATAL: ThreadSanitizer can not mmap the shadow memory (" + Printf("FATAL: ThreadSanitizer can not mmap the shadow memory (" "something is mapped at 0x%zx < 0x%zx)\n", start, kLinuxAppMemBeg); - TsanPrintf("FATAL: Make sure to compile with -fPIE" + Printf("FATAL: Make sure to compile with -fPIE" " and to link with -pie.\n"); Die(); } } } +static void InitDataSeg() { + MemoryMappingLayout proc_maps; + uptr start, end, offset; + char name[128]; + bool prev_is_data = false; + while (proc_maps.Next(&start, &end, &offset, name, ARRAY_SIZE(name))) { + DPrintf("%p-%p %p %s\n", start, end, offset, name); + bool is_data = offset != 0 && name[0] != 0; + // BSS may get merged with [heap] in /proc/self/maps. This is not very + // reliable. + bool is_bss = offset == 0 && + (name[0] == 0 || internal_strcmp(name, "[heap]") == 0) && prev_is_data; + if (g_data_start == 0 && is_data) + g_data_start = start; + if (is_bss) + g_data_end = end; + prev_is_data = is_data; + } + DPrintf("guessed data_start=%p data_end=%p\n", g_data_start, g_data_end); + CHECK_LT(g_data_start, g_data_end); + CHECK_GE((uptr)&g_data_start, g_data_start); + CHECK_LT((uptr)&g_data_start, g_data_end); +} + static uptr g_tls_size; #ifdef __i386__ @@ -157,14 +170,14 @@ static uptr g_tls_size; #else # define INTERNAL_FUNCTION #endif -extern "C" void _dl_get_tls_static_info(size_t*, size_t*) - __attribute__((weak)) INTERNAL_FUNCTION; static int InitTlsSize() { typedef void (*get_tls_func)(size_t*, size_t*) INTERNAL_FUNCTION; - get_tls_func get_tls = &_dl_get_tls_static_info; - if (get_tls == 0) - get_tls = (get_tls_func)dlsym(RTLD_NEXT, "_dl_get_tls_static_info"); + get_tls_func get_tls; + void *get_tls_static_info_ptr = dlsym(RTLD_NEXT, "_dl_get_tls_static_info"); + CHECK_EQ(sizeof(get_tls), sizeof(get_tls_static_info_ptr)); + internal_memcpy(&get_tls, &get_tls_static_info_ptr, + sizeof(get_tls_static_info_ptr)); CHECK_NE(get_tls, 0); size_t tls_size = 0; size_t tls_align = 0; @@ -173,22 +186,62 @@ static int InitTlsSize() { } #endif // #ifndef TSAN_GO +static rlim_t getlim(int res) { + rlimit rlim; + CHECK_EQ(0, getrlimit(res, &rlim)); + return rlim.rlim_cur; +} + +static void setlim(int res, rlim_t lim) { + // The following magic is to prevent clang from replacing it with memset. + volatile rlimit rlim; + rlim.rlim_cur = lim; + rlim.rlim_max = lim; + setrlimit(res, (rlimit*)&rlim); +} + const char *InitializePlatform() { void *p = 0; if (sizeof(p) == 8) { // Disable core dumps, dumping of 16TB usually takes a bit long. - // The following magic is to prevent clang from replacing it with memset. - volatile rlimit lim; - lim.rlim_cur = 0; - lim.rlim_max = 0; - setrlimit(RLIMIT_CORE, (rlimit*)&lim); + setlim(RLIMIT_CORE, 0); + } + + // Go maps shadow memory lazily and works fine with limited address space. + // Unlimited stack is not a problem as well, because the executable + // is not compiled with -pie. + if (kCppMode) { + bool reexec = false; + // TSan doesn't play well with unlimited stack size (as stack + // overlaps with shadow memory). If we detect unlimited stack size, + // we re-exec the program with limited stack size as a best effort. + if (getlim(RLIMIT_STACK) == (rlim_t)-1) { + const uptr kMaxStackSize = 32 * 1024 * 1024; + Report("WARNING: Program is run with unlimited stack size, which " + "wouldn't work with ThreadSanitizer.\n"); + Report("Re-execing with stack size limited to %zd bytes.\n", + kMaxStackSize); + SetStackSizeLimitInBytes(kMaxStackSize); + reexec = true; + } + + if (getlim(RLIMIT_AS) != (rlim_t)-1) { + Report("WARNING: Program is run with limited virtual address space," + " which wouldn't work with ThreadSanitizer.\n"); + Report("Re-execing with unlimited virtual address space.\n"); + setlim(RLIMIT_AS, -1); + reexec = true; + } + if (reexec) + ReExec(); } #ifndef TSAN_GO CheckPIE(); g_tls_size = (uptr)InitTlsSize(); + InitDataSeg(); #endif - return getenv("TSAN_OPTIONS"); + return getenv(kTsanOptionsEnv); } void FinalizePlatform() { @@ -232,6 +285,9 @@ void GetThreadStackAndTls(bool main, uptr *stk_addr, uptr *stk_size, #endif } +bool IsGlobalVar(uptr addr) { + return g_data_start && addr >= g_data_start && addr < g_data_end; +} } // namespace __tsan diff --git a/lib/tsan/rtl/tsan_platform_mac.cc b/lib/tsan/rtl/tsan_platform_mac.cc index 7451492744f2..183061d14638 100644 --- a/lib/tsan/rtl/tsan_platform_mac.cc +++ b/lib/tsan/rtl/tsan_platform_mac.cc @@ -37,14 +37,6 @@ #include <errno.h> #include <sched.h> -namespace __sanitizer { - -void Die() { - _exit(1); -} - -} // namespace __sanitizer - namespace __tsan { ScopedInRtl::ScopedInRtl() { @@ -60,13 +52,14 @@ uptr GetShadowMemoryConsumption() { void FlushShadowMemory() { } +#ifndef TSAN_GO void InitializeShadowMemory() { uptr shadow = (uptr)MmapFixedNoReserve(kLinuxShadowBeg, kLinuxShadowEnd - kLinuxShadowBeg); if (shadow != kLinuxShadowBeg) { - TsanPrintf("FATAL: ThreadSanitizer can not mmap the shadow memory\n"); - TsanPrintf("FATAL: Make sure to compile with -fPIE and " - "to link with -pie.\n"); + Printf("FATAL: ThreadSanitizer can not mmap the shadow memory\n"); + Printf("FATAL: Make sure to compile with -fPIE and " + "to link with -pie.\n"); Die(); } DPrintf("kLinuxShadow %zx-%zx (%zuGB)\n", @@ -76,6 +69,7 @@ void InitializeShadowMemory() { kLinuxAppMemBeg, kLinuxAppMemEnd, (kLinuxAppMemEnd - kLinuxAppMemBeg) >> 30); } +#endif const char *InitializePlatform() { void *p = 0; @@ -88,7 +82,7 @@ const char *InitializePlatform() { setrlimit(RLIMIT_CORE, (rlimit*)&lim); } - return getenv("TSAN_OPTIONS"); + return getenv(kTsanOptionsEnv); } void FinalizePlatform() { diff --git a/lib/tsan/rtl/tsan_platform_windows.cc b/lib/tsan/rtl/tsan_platform_windows.cc new file mode 100644 index 000000000000..f23e84e7875d --- /dev/null +++ b/lib/tsan/rtl/tsan_platform_windows.cc @@ -0,0 +1,58 @@ +//===-- tsan_platform_windows.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 (TSan), a race detector. +// +// Windows-specific code. +//===----------------------------------------------------------------------===// + +#ifdef _WIN32 + +#include "tsan_platform.h" + +#include <stdlib.h> + +namespace __tsan { + +ScopedInRtl::ScopedInRtl() { +} + +ScopedInRtl::~ScopedInRtl() { +} + +uptr GetShadowMemoryConsumption() { + return 0; +} + +void FlushShadowMemory() { +} + +const char *InitializePlatform() { + return getenv(kTsanOptionsEnv); +} + +void FinalizePlatform() { + fflush(0); +} + +uptr GetTlsSize() { + return 0; +} + +void GetThreadStackAndTls(bool main, uptr *stk_addr, uptr *stk_size, + uptr *tls_addr, uptr *tls_size) { + *stk_addr = 0; + *stk_size = 0; + *tls_addr = 0; + *tls_size = 0; +} + +} // namespace __tsan + +#endif // #ifdef _WIN32 diff --git a/lib/tsan/rtl/tsan_printf.cc b/lib/tsan/rtl/tsan_printf.cc deleted file mode 100644 index 6f41440fb929..000000000000 --- a/lib/tsan/rtl/tsan_printf.cc +++ /dev/null @@ -1,39 +0,0 @@ -//===-- tsan_printf.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 (TSan), a race detector. -// -//===----------------------------------------------------------------------===// - -#include "sanitizer_common/sanitizer_libc.h" -#include "tsan_defs.h" -#include "tsan_mman.h" -#include "tsan_platform.h" - -#include <stdarg.h> // va_list - -namespace __sanitizer { -int VSNPrintf(char *buff, int buff_length, const char *format, va_list args); -} // namespace __sanitizer - -namespace __tsan { - -void TsanPrintf(const char *format, ...) { - ScopedInRtl in_rtl; - const uptr kMaxLen = 16 * 1024; - InternalScopedBuf<char> buffer(kMaxLen); - va_list args; - va_start(args, format); - uptr len = VSNPrintf(buffer, buffer.Size(), format, args); - va_end(args); - internal_write(CTX() ? flags()->log_fileno : 2, - buffer, len < buffer.Size() ? len : buffer.Size() - 1); -} - -} // namespace __tsan diff --git a/lib/tsan/rtl/tsan_report.cc b/lib/tsan/rtl/tsan_report.cc index c841a9827f4c..056dc97387b9 100644 --- a/lib/tsan/rtl/tsan_report.cc +++ b/lib/tsan/rtl/tsan_report.cc @@ -21,100 +21,153 @@ ReportDesc::ReportDesc() , mops(MBlockReportMop) , locs(MBlockReportLoc) , mutexes(MBlockReportMutex) - , threads(MBlockReportThread) { + , threads(MBlockReportThread) + , sleep() { +} + +ReportMop::ReportMop() + : mset(MBlockReportMutex) { } ReportDesc::~ReportDesc() { + // FIXME(dvyukov): it must be leaking a lot of memory. } #ifndef TSAN_GO +const int kThreadBufSize = 32; +const char *thread_name(char *buf, int tid) { + if (tid == 0) + return "main thread"; + internal_snprintf(buf, kThreadBufSize, "thread T%d", tid); + return buf; +} + static void PrintHeader(ReportType typ) { - TsanPrintf("WARNING: ThreadSanitizer: "); + Printf("WARNING: ThreadSanitizer: "); if (typ == ReportTypeRace) - TsanPrintf("data race"); + Printf("data race"); else if (typ == ReportTypeUseAfterFree) - TsanPrintf("heap-use-after-free"); + Printf("heap-use-after-free"); else if (typ == ReportTypeThreadLeak) - TsanPrintf("thread leak"); + Printf("thread leak"); else if (typ == ReportTypeMutexDestroyLocked) - TsanPrintf("destroy of a locked mutex"); + Printf("destroy of a locked mutex"); else if (typ == ReportTypeSignalUnsafe) - TsanPrintf("signal-unsafe call inside of a signal"); + Printf("signal-unsafe call inside of a signal"); else if (typ == ReportTypeErrnoInSignal) - TsanPrintf("signal handler spoils errno"); + Printf("signal handler spoils errno"); - TsanPrintf(" (pid=%d)\n", GetPid()); + Printf(" (pid=%d)\n", GetPid()); } -static void PrintStack(const ReportStack *ent) { +void PrintStack(const ReportStack *ent) { + if (ent == 0) { + Printf(" [failed to restore the stack]\n\n"); + return; + } for (int i = 0; ent; ent = ent->next, i++) { - TsanPrintf(" #%d %s %s:%d", i, ent->func, ent->file, ent->line); + Printf(" #%d %s %s:%d", i, ent->func, ent->file, ent->line); if (ent->col) - TsanPrintf(":%d", ent->col); + Printf(":%d", ent->col); if (ent->module && ent->offset) - TsanPrintf(" (%s+%p)\n", ent->module, (void*)ent->offset); + Printf(" (%s+%p)\n", ent->module, (void*)ent->offset); else - TsanPrintf(" (%p)\n", (void*)ent->pc); + Printf(" (%p)\n", (void*)ent->pc); + } + Printf("\n"); +} + +static void PrintMutexSet(Vector<ReportMopMutex> const& mset) { + for (uptr i = 0; i < mset.Size(); i++) { + if (i == 0) + Printf(" (mutexes:"); + const ReportMopMutex m = mset[i]; + Printf(" %s M%llu", m.write ? "write" : "read", m.id); + Printf(i == mset.Size() - 1 ? ")" : ","); } } static void PrintMop(const ReportMop *mop, bool first) { - TsanPrintf(" %s of size %d at %p", + char thrbuf[kThreadBufSize]; + Printf(" %s of size %d at %p by %s", (first ? (mop->write ? "Write" : "Read") : (mop->write ? "Previous write" : "Previous read")), - mop->size, (void*)mop->addr); - if (mop->tid == 0) - TsanPrintf(" by main thread:\n"); - else - TsanPrintf(" by thread %d:\n", mop->tid); + mop->size, (void*)mop->addr, + thread_name(thrbuf, mop->tid)); + PrintMutexSet(mop->mset); + Printf(":\n"); PrintStack(mop->stack); } static void PrintLocation(const ReportLocation *loc) { + char thrbuf[kThreadBufSize]; if (loc->type == ReportLocationGlobal) { - TsanPrintf(" Location is global '%s' of size %zu at %zx %s:%d\n", - loc->name, loc->size, loc->addr, loc->file, loc->line); + Printf(" Location is global '%s' of size %zu at %zx (%s+%p)\n\n", + loc->name, loc->size, loc->addr, loc->module, loc->offset); } else if (loc->type == ReportLocationHeap) { - TsanPrintf(" Location is heap of size %zu at %zx allocated " - "by thread %d:\n", loc->size, loc->addr, loc->tid); + char thrbuf[kThreadBufSize]; + Printf(" Location is heap block of size %zu at %p allocated by %s:\n", + loc->size, loc->addr, thread_name(thrbuf, loc->tid)); PrintStack(loc->stack); } else if (loc->type == ReportLocationStack) { - TsanPrintf(" Location is stack of thread %d:\n", loc->tid); + Printf(" Location is stack of %s.\n\n", thread_name(thrbuf, loc->tid)); + } else if (loc->type == ReportLocationTLS) { + Printf(" Location is TLS of %s.\n\n", thread_name(thrbuf, loc->tid)); + } else if (loc->type == ReportLocationFD) { + Printf(" Location is file descriptor %d created by %s at:\n", + loc->fd, thread_name(thrbuf, loc->tid)); + PrintStack(loc->stack); } } static void PrintMutex(const ReportMutex *rm) { - if (rm->stack == 0) - return; - TsanPrintf(" Mutex %d created at:\n", rm->id); - PrintStack(rm->stack); + if (rm->destroyed) { + Printf(" Mutex M%llu is already destroyed.\n\n", rm->id); + } else { + Printf(" Mutex M%llu created at:\n", rm->id); + PrintStack(rm->stack); + } } static void PrintThread(const ReportThread *rt) { if (rt->id == 0) // Little sense in describing the main thread. return; - TsanPrintf(" Thread %d", rt->id); + Printf(" Thread T%d", rt->id); if (rt->name) - TsanPrintf(" '%s'", rt->name); - TsanPrintf(" (%s)", rt->running ? "running" : "finished"); + Printf(" '%s'", rt->name); + char thrbuf[kThreadBufSize]; + Printf(" (tid=%zu, %s) created by %s", + rt->pid, rt->running ? "running" : "finished", + thread_name(thrbuf, rt->parent_tid)); if (rt->stack) - TsanPrintf(" created at:"); - TsanPrintf("\n"); + Printf(" at:"); + Printf("\n"); PrintStack(rt->stack); } +static void PrintSleep(const ReportStack *s) { + Printf(" As if synchronized via sleep:\n"); + PrintStack(s); +} + void PrintReport(const ReportDesc *rep) { - TsanPrintf("==================\n"); + Printf("==================\n"); PrintHeader(rep->typ); - for (uptr i = 0; i < rep->stacks.Size(); i++) + for (uptr i = 0; i < rep->stacks.Size(); i++) { + if (i) + Printf(" and:\n"); PrintStack(rep->stacks[i]); + } for (uptr i = 0; i < rep->mops.Size(); i++) PrintMop(rep->mops[i], i == 0); + if (rep->sleep) + PrintSleep(rep->sleep); + for (uptr i = 0; i < rep->locs.Size(); i++) PrintLocation(rep->locs[i]); @@ -124,20 +177,25 @@ void PrintReport(const ReportDesc *rep) { for (uptr i = 0; i < rep->threads.Size(); i++) PrintThread(rep->threads[i]); - TsanPrintf("==================\n"); + Printf("==================\n"); } #else -static void PrintStack(const ReportStack *ent) { +void PrintStack(const ReportStack *ent) { + if (ent == 0) { + Printf(" [failed to restore the stack]\n\n"); + return; + } for (int i = 0; ent; ent = ent->next, i++) { - TsanPrintf(" %s()\n %s:%d +0x%zx\n", + Printf(" %s()\n %s:%d +0x%zx\n", ent->func, ent->file, ent->line, (void*)ent->offset); } + Printf("\n"); } static void PrintMop(const ReportMop *mop, bool first) { - TsanPrintf("%s by goroutine %d:\n", + Printf("%s by goroutine %d:\n", (first ? (mop->write ? "Write" : "Read") : (mop->write ? "Previous write" : "Previous read")), mop->tid); @@ -147,19 +205,19 @@ static void PrintMop(const ReportMop *mop, bool first) { static void PrintThread(const ReportThread *rt) { if (rt->id == 0) // Little sense in describing the main thread. return; - TsanPrintf("Goroutine %d (%s) created at:\n", + Printf("Goroutine %d (%s) created at:\n", rt->id, rt->running ? "running" : "finished"); PrintStack(rt->stack); } void PrintReport(const ReportDesc *rep) { - TsanPrintf("==================\n"); - TsanPrintf("WARNING: DATA RACE at %p\n", (void*)rep->mops[0]->addr); + Printf("==================\n"); + Printf("WARNING: DATA RACE\n"); for (uptr i = 0; i < rep->mops.Size(); i++) PrintMop(rep->mops[i], i == 0); for (uptr i = 0; i < rep->threads.Size(); i++) PrintThread(rep->threads[i]); - TsanPrintf("==================\n"); + Printf("==================\n"); } #endif diff --git a/lib/tsan/rtl/tsan_report.h b/lib/tsan/rtl/tsan_report.h index d139296d54c0..f6715d1aae9b 100644 --- a/lib/tsan/rtl/tsan_report.h +++ b/lib/tsan/rtl/tsan_report.h @@ -24,7 +24,7 @@ enum ReportType { ReportTypeThreadLeak, ReportTypeMutexDestroyLocked, ReportTypeSignalUnsafe, - ReportTypeErrnoInSignal, + ReportTypeErrnoInSignal }; struct ReportStack { @@ -38,27 +38,38 @@ struct ReportStack { int col; }; +struct ReportMopMutex { + u64 id; + bool write; +}; + struct ReportMop { int tid; uptr addr; int size; bool write; - int nmutex; - int *mutex; + Vector<ReportMopMutex> mset; ReportStack *stack; + + ReportMop(); }; enum ReportLocationType { ReportLocationGlobal, ReportLocationHeap, ReportLocationStack, + ReportLocationTLS, + ReportLocationFD }; struct ReportLocation { ReportLocationType type; uptr addr; uptr size; + char *module; + uptr offset; int tid; + int fd; char *name; char *file; int line; @@ -67,13 +78,16 @@ struct ReportLocation { struct ReportThread { int id; + uptr pid; bool running; char *name; + int parent_tid; ReportStack *stack; }; struct ReportMutex { - int id; + u64 id; + bool destroyed; ReportStack *stack; }; @@ -85,6 +99,7 @@ class ReportDesc { Vector<ReportLocation*> locs; Vector<ReportMutex*> mutexes; Vector<ReportThread*> threads; + ReportStack *sleep; ReportDesc(); ~ReportDesc(); @@ -96,6 +111,7 @@ class ReportDesc { // Format and output the report to the console/log. No additional logic. void PrintReport(const ReportDesc *rep); +void PrintStack(const ReportStack *stack); } // namespace __tsan diff --git a/lib/tsan/rtl/tsan_rtl.cc b/lib/tsan/rtl/tsan_rtl.cc index 0ceb26c90e67..493ed2055dfc 100644 --- a/lib/tsan/rtl/tsan_rtl.cc +++ b/lib/tsan/rtl/tsan_rtl.cc @@ -15,7 +15,9 @@ #include "sanitizer_common/sanitizer_atomic.h" #include "sanitizer_common/sanitizer_common.h" #include "sanitizer_common/sanitizer_libc.h" +#include "sanitizer_common/sanitizer_stackdepot.h" #include "sanitizer_common/sanitizer_placement_new.h" +#include "sanitizer_common/sanitizer_symbolizer.h" #include "tsan_defs.h" #include "tsan_platform.h" #include "tsan_rtl.h" @@ -47,11 +49,12 @@ Context::Context() , nmissed_expected() , thread_mtx(MutexTypeThreads, StatMtxThreads) , racy_stacks(MBlockRacyStacks) - , racy_addresses(MBlockRacyAddresses) { + , racy_addresses(MBlockRacyAddresses) + , fired_suppressions(MBlockRacyAddresses) { } // The objects are allocated in TLS, so one may rely on zero-initialization. -ThreadState::ThreadState(Context *ctx, int tid, u64 epoch, +ThreadState::ThreadState(Context *ctx, int tid, int unique_id, u64 epoch, uptr stk_addr, uptr stk_size, uptr tls_addr, uptr tls_size) : fast_state(tid, epoch) @@ -62,6 +65,7 @@ ThreadState::ThreadState(Context *ctx, int tid, u64 epoch, // , in_rtl() , shadow_stack_pos(&shadow_stack[0]) , tid(tid) + , unique_id(unique_id) , stk_addr(stk_addr) , stk_size(stk_size) , tls_addr(tls_addr) @@ -71,6 +75,7 @@ ThreadState::ThreadState(Context *ctx, int tid, u64 epoch, ThreadContext::ThreadContext(int tid) : tid(tid) , unique_id() + , os_id() , user_id() , thr() , status(ThreadStatusInvalid) @@ -79,7 +84,8 @@ ThreadContext::ThreadContext(int tid) , epoch0() , epoch1() , dead_info() - , dead_next() { + , dead_next() + , name() { } static void WriteMemoryProfile(char *buf, uptr buf_size, int num) { @@ -119,9 +125,9 @@ static void MemoryProfileThread(void *arg) { ScopedInRtl in_rtl; fd_t fd = (fd_t)(uptr)arg; for (int i = 0; ; i++) { - InternalScopedBuf<char> buf(4096); - WriteMemoryProfile(buf.Ptr(), buf.Size(), i); - internal_write(fd, buf.Ptr(), internal_strlen(buf.Ptr())); + InternalScopedBuffer<char> buf(4096); + WriteMemoryProfile(buf.data(), buf.size(), i); + internal_write(fd, buf.data(), internal_strlen(buf.data())); SleepForSeconds(1); } } @@ -129,12 +135,12 @@ static void MemoryProfileThread(void *arg) { static void InitializeMemoryProfile() { if (flags()->profile_memory == 0 || flags()->profile_memory[0] == 0) return; - InternalScopedBuf<char> filename(4096); - internal_snprintf(filename.Ptr(), filename.Size(), "%s.%d", + InternalScopedBuffer<char> filename(4096); + internal_snprintf(filename.data(), filename.size(), "%s.%d", flags()->profile_memory, GetPid()); - fd_t fd = internal_open(filename.Ptr(), true); + fd_t fd = internal_open(filename.data(), true); if (fd == kInvalidFd) { - TsanPrintf("Failed to open memory profile file '%s'\n", &filename[0]); + Printf("Failed to open memory profile file '%s'\n", &filename[0]); Die(); } internal_start_thread(&MemoryProfileThread, (void*)(uptr)fd); @@ -156,41 +162,81 @@ static void InitializeMemoryFlush() { internal_start_thread(&MemoryFlushThread, 0); } +void MapShadow(uptr addr, uptr size) { + MmapFixedNoReserve(MemToShadow(addr), size * kShadowMultiplier); +} + +void MapThreadTrace(uptr addr, uptr size) { + DPrintf("#0: Mapping trace at %p-%p(0x%zx)\n", addr, addr + size, size); + CHECK_GE(addr, kTraceMemBegin); + CHECK_LE(addr + size, kTraceMemBegin + kTraceMemSize); + if (addr != (uptr)MmapFixedNoReserve(addr, size)) { + Printf("FATAL: ThreadSanitizer can not mmap thread trace\n"); + Die(); + } +} + void Initialize(ThreadState *thr) { // Thread safe because done before all threads exist. static bool is_initialized = false; if (is_initialized) return; is_initialized = true; + // Install tool-specific callbacks in sanitizer_common. + SetCheckFailedCallback(TsanCheckFailed); + ScopedInRtl in_rtl; +#ifndef TSAN_GO + InitializeAllocator(); +#endif InitializeInterceptors(); const char *env = InitializePlatform(); InitializeMutex(); InitializeDynamicAnnotations(); ctx = new(ctx_placeholder) Context; +#ifndef TSAN_GO InitializeShadowMemory(); +#endif ctx->dead_list_size = 0; ctx->dead_list_head = 0; ctx->dead_list_tail = 0; InitializeFlags(&ctx->flags, env); + // Setup correct file descriptor for error reports. + if (internal_strcmp(flags()->log_path, "stdout") == 0) + __sanitizer_set_report_fd(kStdoutFd); + else if (internal_strcmp(flags()->log_path, "stderr") == 0) + __sanitizer_set_report_fd(kStderrFd); + else + __sanitizer_set_report_path(flags()->log_path); InitializeSuppressions(); +#ifndef TSAN_GO + // Initialize external symbolizer before internal threads are started. + const char *external_symbolizer = flags()->external_symbolizer_path; + if (external_symbolizer != 0 && external_symbolizer[0] != '\0') { + if (!InitializeExternalSymbolizer(external_symbolizer)) { + Printf("Failed to start external symbolizer: '%s'\n", + external_symbolizer); + Die(); + } + } +#endif InitializeMemoryProfile(); InitializeMemoryFlush(); if (ctx->flags.verbosity) - TsanPrintf("***** Running under ThreadSanitizer v2 (pid %d) *****\n", + Printf("***** Running under ThreadSanitizer v2 (pid %d) *****\n", GetPid()); // Initialize thread 0. ctx->thread_seq = 0; int tid = ThreadCreate(thr, 0, 0, true); CHECK_EQ(tid, 0); - ThreadStart(thr, tid); + ThreadStart(thr, tid, GetPid()); CHECK_EQ(thr->in_rtl, 1); ctx->initialized = true; if (flags()->stop_on_start) { - TsanPrintf("ThreadSanitizer is suspended at startup (pid %d)." + Printf("ThreadSanitizer is suspended at startup (pid %d)." " Call __tsan_resume().\n", GetPid()); while (__tsan_resumed == 0); @@ -202,34 +248,77 @@ int Finalize(ThreadState *thr) { Context *ctx = __tsan::ctx; bool failed = false; + if (flags()->atexit_sleep_ms > 0 && ThreadCount(thr) > 1) + SleepForMillis(flags()->atexit_sleep_ms); + + // Wait for pending reports. + ctx->report_mtx.Lock(); + ctx->report_mtx.Unlock(); + ThreadFinalize(thr); if (ctx->nreported) { failed = true; - TsanPrintf("ThreadSanitizer: reported %d warnings\n", ctx->nreported); +#ifndef TSAN_GO + Printf("ThreadSanitizer: reported %d warnings\n", ctx->nreported); +#else + Printf("Found %d data race(s)\n", ctx->nreported); +#endif } if (ctx->nmissed_expected) { failed = true; - TsanPrintf("ThreadSanitizer: missed %d expected races\n", + Printf("ThreadSanitizer: missed %d expected races\n", ctx->nmissed_expected); } + StatAggregate(ctx->stat, thr->stat); StatOutput(ctx->stat); return failed ? flags()->exitcode : 0; } +#ifndef TSAN_GO +u32 CurrentStackId(ThreadState *thr, uptr pc) { + if (thr->shadow_stack_pos == 0) // May happen during bootstrap. + return 0; + if (pc) { + thr->shadow_stack_pos[0] = pc; + thr->shadow_stack_pos++; + } + u32 id = StackDepotPut(thr->shadow_stack, + thr->shadow_stack_pos - thr->shadow_stack); + if (pc) + thr->shadow_stack_pos--; + return id; +} +#endif + void TraceSwitch(ThreadState *thr) { thr->nomalloc++; ScopedInRtl in_rtl; Lock l(&thr->trace.mtx); - unsigned trace = (thr->fast_state.epoch() / kTracePartSize) % kTraceParts; + unsigned trace = (thr->fast_state.epoch() / kTracePartSize) % TraceParts(); TraceHeader *hdr = &thr->trace.headers[trace]; hdr->epoch0 = thr->fast_state.epoch(); hdr->stack0.ObtainCurrent(thr, 0); + hdr->mset0 = thr->mset; thr->nomalloc--; } +uptr TraceTopPC(ThreadState *thr) { + Event *events = (Event*)GetThreadTrace(thr->tid); + uptr pc = events[thr->fast_state.GetTracePos()]; + return pc; +} + +uptr TraceSize() { + return (uptr)(1ull << (kTracePartSizeBits + flags()->history_size + 1)); +} + +uptr TraceParts() { + return TraceSize() / kTracePartSize; +} + #ifndef TSAN_GO extern "C" void __tsan_trace_switch() { TraceSwitch(cur_thread()); @@ -273,11 +362,11 @@ static inline bool BothReads(Shadow s, int kAccessIsWrite) { return !kAccessIsWrite && !s.is_write(); } -static inline bool OldIsRWStronger(Shadow old, int kAccessIsWrite) { +static inline bool OldIsRWNotWeaker(Shadow old, int kAccessIsWrite) { return old.is_write() || !kAccessIsWrite; } -static inline bool OldIsRWWeaker(Shadow old, int kAccessIsWrite) { +static inline bool OldIsRWWeakerOrEqual(Shadow old, int kAccessIsWrite) { return !old.is_write() || kAccessIsWrite; } @@ -286,12 +375,12 @@ static inline bool OldIsInSameSynchEpoch(Shadow old, ThreadState *thr) { } static inline bool HappensBefore(Shadow old, ThreadState *thr) { - return thr->clock.get(old.tid()) >= old.epoch(); + return thr->clock.get(old.TidWithIgnore()) >= old.epoch(); } ALWAYS_INLINE void MemoryAccessImpl(ThreadState *thr, uptr addr, - int kAccessSizeLog, bool kAccessIsWrite, FastState fast_state, + int kAccessSizeLog, bool kAccessIsWrite, u64 *shadow_mem, Shadow cur) { StatInc(thr, StatMop); StatInc(thr, kAccessIsWrite ? StatMopWrite : StatMopRead); @@ -367,7 +456,7 @@ ALWAYS_INLINE void MemoryAccess(ThreadState *thr, uptr pc, uptr addr, int kAccessSizeLog, bool kAccessIsWrite) { u64 *shadow_mem = (u64*)MemToShadow(addr); - DPrintf2("#%d: tsan::OnMemoryAccess: @%p %p size=%d" + DPrintf2("#%d: MemoryAccess: @%p %p size=%d" " is_write=%d shadow_mem=%p {%zx, %zx, %zx, %zx}\n", (int)thr->fast_state.tid(), (void*)pc, (void*)addr, (int)(1 << kAccessSizeLog), kAccessIsWrite, shadow_mem, @@ -375,11 +464,11 @@ void MemoryAccess(ThreadState *thr, uptr pc, uptr addr, (uptr)shadow_mem[2], (uptr)shadow_mem[3]); #if TSAN_DEBUG if (!IsAppMem(addr)) { - TsanPrintf("Access to non app mem %zx\n", addr); + Printf("Access to non app mem %zx\n", addr); DCHECK(IsAppMem(addr)); } if (!IsShadowMem((uptr)shadow_mem)) { - TsanPrintf("Bad shadow addr %p (%zx)\n", shadow_mem, addr); + Printf("Bad shadow addr %p (%zx)\n", shadow_mem, addr); DCHECK(IsShadowMem((uptr)shadow_mem)); } #endif @@ -395,9 +484,9 @@ void MemoryAccess(ThreadState *thr, uptr pc, uptr addr, // We must not store to the trace if we do not store to the shadow. // That is, this call must be moved somewhere below. - TraceAddEvent(thr, fast_state.epoch(), EventTypeMop, pc); + TraceAddEvent(thr, fast_state, EventTypeMop, pc); - MemoryAccessImpl(thr, addr, kAccessSizeLog, kAccessIsWrite, fast_state, + MemoryAccessImpl(thr, addr, kAccessSizeLog, kAccessIsWrite, shadow_mem, cur); } @@ -414,24 +503,29 @@ static void MemoryRangeSet(ThreadState *thr, uptr pc, uptr addr, uptr size, addr += offset; size -= offset; } - CHECK_EQ(addr % 8, 0); - CHECK(IsAppMem(addr)); - CHECK(IsAppMem(addr + size - 1)); + DCHECK_EQ(addr % 8, 0); + // If a user passes some insane arguments (memset(0)), + // let it just crash as usual. + if (!IsAppMem(addr) || !IsAppMem(addr + size - 1)) + return; (void)thr; (void)pc; // Some programs mmap like hundreds of GBs but actually used a small part. // So, it's better to report a false positive on the memory // then to hang here senselessly. - const uptr kMaxResetSize = 1024*1024*1024; + const uptr kMaxResetSize = 4ull*1024*1024*1024; if (size > kMaxResetSize) size = kMaxResetSize; - size = (size + 7) & ~7; + size = (size + (kShadowCell - 1)) & ~(kShadowCell - 1); u64 *p = (u64*)MemToShadow(addr); CHECK(IsShadowMem((uptr)p)); CHECK(IsShadowMem((uptr)(p + size * kShadowCnt / kShadowCell - 1))); // FIXME: may overwrite a part outside the region - for (uptr i = 0; i < size * kShadowCnt / kShadowCell; i++) - p[i] = val; + for (uptr i = 0; i < size * kShadowCnt / kShadowCell;) { + p[i++] = val; + for (uptr j = 1; j < kShadowCnt; j++) + p[i++] = 0; + } } void MemoryResetRange(ThreadState *thr, uptr pc, uptr addr, uptr size) { @@ -441,18 +535,28 @@ void MemoryResetRange(ThreadState *thr, uptr pc, uptr addr, uptr size) { void MemoryRangeFreed(ThreadState *thr, uptr pc, uptr addr, uptr size) { MemoryAccessRange(thr, pc, addr, size, true); Shadow s(thr->fast_state); + s.ClearIgnoreBit(); s.MarkAsFreed(); s.SetWrite(true); s.SetAddr0AndSizeLog(0, 3); MemoryRangeSet(thr, pc, addr, size, s.raw()); } +void MemoryRangeImitateWrite(ThreadState *thr, uptr pc, uptr addr, uptr size) { + Shadow s(thr->fast_state); + s.ClearIgnoreBit(); + s.SetWrite(true); + s.SetAddr0AndSizeLog(0, 3); + MemoryRangeSet(thr, pc, addr, size, s.raw()); +} + +ALWAYS_INLINE void FuncEntry(ThreadState *thr, uptr pc) { DCHECK_EQ(thr->in_rtl, 0); StatInc(thr, StatFuncEnter); DPrintf2("#%d: FuncEntry %p\n", (int)thr->fast_state.tid(), (void*)pc); thr->fast_state.IncrementEpoch(); - TraceAddEvent(thr, thr->fast_state.epoch(), EventTypeFuncEnter, pc); + TraceAddEvent(thr, thr->fast_state, EventTypeFuncEnter, pc); // Shadow stack maintenance can be replaced with // stack unwinding during trace switch (which presumably must be faster). @@ -476,12 +580,13 @@ void FuncEntry(ThreadState *thr, uptr pc) { thr->shadow_stack_pos++; } +ALWAYS_INLINE void FuncExit(ThreadState *thr) { DCHECK_EQ(thr->in_rtl, 0); StatInc(thr, StatFuncExit); DPrintf2("#%d: FuncExit\n", (int)thr->fast_state.tid()); thr->fast_state.IncrementEpoch(); - TraceAddEvent(thr, thr->fast_state.epoch(), EventTypeFuncExit, 0); + TraceAddEvent(thr, thr->fast_state, EventTypeFuncExit, 0); DCHECK_GT(thr->shadow_stack_pos, &thr->shadow_stack[0]); #ifndef TSAN_GO diff --git a/lib/tsan/rtl/tsan_rtl.h b/lib/tsan/rtl/tsan_rtl.h index c559cb2f080c..6b0ab0d385ef 100644 --- a/lib/tsan/rtl/tsan_rtl.h +++ b/lib/tsan/rtl/tsan_rtl.h @@ -27,6 +27,7 @@ #define TSAN_RTL_H #include "sanitizer_common/sanitizer_common.h" +#include "sanitizer_common/sanitizer_allocator.h" #include "tsan_clock.h" #include "tsan_defs.h" #include "tsan_flags.h" @@ -34,31 +35,90 @@ #include "tsan_trace.h" #include "tsan_vector.h" #include "tsan_report.h" +#include "tsan_platform.h" +#include "tsan_mutexset.h" + +#if SANITIZER_WORDSIZE != 64 +# error "ThreadSanitizer is supported only on 64-bit platforms" +#endif namespace __tsan { -void TsanPrintf(const char *format, ...); +// Descriptor of user's memory block. +struct MBlock { + Mutex mtx; + uptr size; + u32 alloc_tid; + u32 alloc_stack_id; + SyncVar *head; + + MBlock() + : mtx(MutexTypeMBlock, StatMtxMBlock) { + } +}; + +#ifndef TSAN_GO +#if defined(TSAN_COMPAT_SHADOW) && TSAN_COMPAT_SHADOW +const uptr kAllocatorSpace = 0x7d0000000000ULL; +#else +const uptr kAllocatorSpace = 0x7d0000000000ULL; +#endif +const uptr kAllocatorSize = 0x10000000000ULL; // 1T. + +struct TsanMapUnmapCallback { + void OnMap(uptr p, uptr size) const { } + void OnUnmap(uptr p, uptr size) const { + // We are about to unmap a chunk of user memory. + // Mark the corresponding shadow memory as not needed. + uptr shadow_beg = MemToShadow(p); + uptr shadow_end = MemToShadow(p + size); + CHECK(IsAligned(shadow_end|shadow_beg, GetPageSizeCached())); + FlushUnneededShadowMemory(shadow_beg, shadow_end - shadow_beg); + } +}; + +typedef SizeClassAllocator64<kAllocatorSpace, kAllocatorSize, sizeof(MBlock), + DefaultSizeClassMap> PrimaryAllocator; +typedef SizeClassAllocatorLocalCache<PrimaryAllocator> AllocatorCache; +typedef LargeMmapAllocator<TsanMapUnmapCallback> SecondaryAllocator; +typedef CombinedAllocator<PrimaryAllocator, AllocatorCache, + SecondaryAllocator> Allocator; +Allocator *allocator(); +#endif + +void TsanCheckFailed(const char *file, int line, const char *cond, + u64 v1, u64 v2); // FastState (from most significant bit): -// unused : 1 +// ignore : 1 // tid : kTidBits // epoch : kClkBits // unused : - -// ignore_bit : 1 +// history_size : 3 class FastState { public: FastState(u64 tid, u64 epoch) { x_ = tid << kTidShift; x_ |= epoch << kClkShift; - DCHECK(tid == this->tid()); - DCHECK(epoch == this->epoch()); + DCHECK_EQ(tid, this->tid()); + DCHECK_EQ(epoch, this->epoch()); + DCHECK_EQ(GetIgnoreBit(), false); } explicit FastState(u64 x) : x_(x) { } + u64 raw() const { + return x_; + } + u64 tid() const { + u64 res = (x_ & ~kIgnoreBit) >> kTidShift; + return res; + } + + u64 TidWithIgnore() const { u64 res = x_ >> kTidShift; return res; } @@ -77,13 +137,34 @@ class FastState { void SetIgnoreBit() { x_ |= kIgnoreBit; } void ClearIgnoreBit() { x_ &= ~kIgnoreBit; } - bool GetIgnoreBit() const { return x_ & kIgnoreBit; } + bool GetIgnoreBit() const { return (s64)x_ < 0; } + + void SetHistorySize(int hs) { + CHECK_GE(hs, 0); + CHECK_LE(hs, 7); + x_ = (x_ & ~7) | hs; + } + + int GetHistorySize() const { + return (int)(x_ & 7); + } + + void ClearHistorySize() { + x_ &= ~7; + } + + u64 GetTracePos() const { + const int hs = GetHistorySize(); + // When hs == 0, the trace consists of 2 parts. + const u64 mask = (1ull << (kTracePartSizeBits + hs + 1)) - 1; + return epoch() & mask; + } private: friend class Shadow; static const int kTidShift = 64 - kTidBits - 1; static const int kClkShift = kTidShift - kClkBits; - static const u64 kIgnoreBit = 1ull; + static const u64 kIgnoreBit = 1ull << 63; static const u64 kFreedBit = 1ull << 63; u64 x_; }; @@ -97,9 +178,14 @@ class FastState { // addr0 : 3 class Shadow : public FastState { public: - explicit Shadow(u64 x) : FastState(x) { } + explicit Shadow(u64 x) + : FastState(x) { + } - explicit Shadow(const FastState &s) : FastState(s.x_) { } + explicit Shadow(const FastState &s) + : FastState(s.x_) { + ClearHistorySize(); + } void SetAddr0AndSizeLog(u64 addr0, unsigned kAccessSizeLog) { DCHECK_EQ(x_ & 31, 0); @@ -118,11 +204,10 @@ class Shadow : public FastState { } bool IsZero() const { return x_ == 0; } - u64 raw() const { return x_; } static inline bool TidsAreEqual(const Shadow s1, const Shadow s2) { u64 shifted_xor = (s1.x_ ^ s2.x_) >> kTidShift; - DCHECK_EQ(shifted_xor == 0, s1.tid() == s2.tid()); + DCHECK_EQ(shifted_xor == 0, s1.TidWithIgnore() == s2.TidWithIgnore()); return shifted_xor == 0; } @@ -199,10 +284,6 @@ class Shadow : public FastState { } }; -// Freed memory. -// As if 8-byte write by thread 0xff..f at epoch 0xff..f, races with everything. -const u64 kShadowFreed = 0xfffffffffffffff8ull; - struct SignalContext; // This struct is stored in TLS. @@ -236,9 +317,14 @@ struct ThreadState { uptr *shadow_stack; uptr *shadow_stack_end; #endif + MutexSet mset; ThreadClock clock; +#ifndef TSAN_GO + AllocatorCache alloc_cache; +#endif u64 stat[StatCnt]; const int tid; + const int unique_id; int in_rtl; bool is_alive; const uptr stk_addr; @@ -251,11 +337,16 @@ struct ThreadState { bool in_signal_handler; SignalContext *signal_ctx; +#ifndef TSAN_GO + u32 last_sleep_stack_id; + ThreadClock last_sleep_clock; +#endif + // Set in regions of runtime that must be signal-safe and fork-safe. // If set, malloc must not be called. int nomalloc; - explicit ThreadState(Context *ctx, int tid, u64 epoch, + explicit ThreadState(Context *ctx, int tid, int unique_id, u64 epoch, uptr stk_addr, uptr stk_size, uptr tls_addr, uptr tls_size); }; @@ -274,7 +365,7 @@ enum ThreadStatus { ThreadStatusCreated, // Created but not yet running. ThreadStatusRunning, // The thread is currently running. ThreadStatusFinished, // Joinable thread is finished but not yet joined. - ThreadStatusDead, // Joined, but some info (trace) is still alive. + ThreadStatusDead // Joined, but some info (trace) is still alive. }; // An info about a thread that is hold for some time after its termination. @@ -285,6 +376,7 @@ struct ThreadDeadInfo { struct ThreadContext { const int tid; int unique_id; // Non-rolling thread id. + uptr os_id; // pid uptr user_id; // Some opaque user thread id (e.g. pthread_t). ThreadState *thr; ThreadStatus status; @@ -297,8 +389,10 @@ struct ThreadContext { u64 epoch0; u64 epoch1; StackTrace creation_stack; + int creation_tid; ThreadDeadInfo *dead_info; ThreadContext *dead_next; // In dead thread list. + char *name; // As annotated by user. explicit ThreadContext(int tid); }; @@ -319,6 +413,11 @@ struct RacyAddress { uptr addr_max; }; +struct FiredSuppression { + ReportType type; + uptr pc; +}; + struct Context { Context(); @@ -342,6 +441,7 @@ struct Context { Vector<RacyStacks> racy_stacks; Vector<RacyAddress> racy_addresses; + Vector<FiredSuppression> fired_suppressions; Flags flags; @@ -366,10 +466,12 @@ class ScopedReport { ~ScopedReport(); void AddStack(const StackTrace *stack); - void AddMemoryAccess(uptr addr, Shadow s, const StackTrace *stack); + void AddMemoryAccess(uptr addr, Shadow s, const StackTrace *stack, + const MutexSet *mset); void AddThread(const ThreadContext *tctx); void AddMutex(const SyncVar *s); void AddLocation(uptr addr, uptr size); + void AddSleep(u32 stack_id); const ReportDesc *GetReport() const; @@ -377,10 +479,14 @@ class ScopedReport { Context *ctx_; ReportDesc *rep_; + void AddMutex(u64 id); + ScopedReport(const ScopedReport&); void operator = (const ScopedReport&); }; +void RestoreStack(int tid, const u64 epoch, StackTrace *stk, MutexSet *mset); + void StatAggregate(u64 *dst, u64 *src); void StatOutput(u64 *stat); void ALWAYS_INLINE INLINE StatInc(ThreadState *thr, StatType typ, u64 n = 1) { @@ -388,34 +494,47 @@ void ALWAYS_INLINE INLINE StatInc(ThreadState *thr, StatType typ, u64 n = 1) { thr->stat[typ] += n; } +void MapShadow(uptr addr, uptr size); +void MapThreadTrace(uptr addr, uptr size); void InitializeShadowMemory(); void InitializeInterceptors(); void InitializeDynamicAnnotations(); void ReportRace(ThreadState *thr); -bool OutputReport(const ScopedReport &srep, +bool OutputReport(Context *ctx, + const ScopedReport &srep, const ReportStack *suppress_stack = 0); +bool IsFiredSuppression(Context *ctx, + const ScopedReport &srep, + const StackTrace &trace); bool IsExpectedReport(uptr addr, uptr size); #if defined(TSAN_DEBUG_OUTPUT) && TSAN_DEBUG_OUTPUT >= 1 -# define DPrintf TsanPrintf +# define DPrintf Printf #else # define DPrintf(...) #endif #if defined(TSAN_DEBUG_OUTPUT) && TSAN_DEBUG_OUTPUT >= 2 -# define DPrintf2 TsanPrintf +# define DPrintf2 Printf #else # define DPrintf2(...) #endif +u32 CurrentStackId(ThreadState *thr, uptr pc); +void PrintCurrentStack(ThreadState *thr, uptr pc); + void Initialize(ThreadState *thr); int Finalize(ThreadState *thr); +SyncVar* GetJavaSync(ThreadState *thr, uptr pc, uptr addr, + bool write_lock, bool create); +SyncVar* GetAndRemoveJavaSync(ThreadState *thr, uptr pc, uptr addr); + void MemoryAccess(ThreadState *thr, uptr pc, uptr addr, int kAccessSizeLog, bool kAccessIsWrite); void MemoryAccessImpl(ThreadState *thr, uptr addr, - int kAccessSizeLog, bool kAccessIsWrite, FastState fast_state, + int kAccessSizeLog, bool kAccessIsWrite, u64 *shadow_mem, Shadow cur); void MemoryRead1Byte(ThreadState *thr, uptr pc, uptr addr); void MemoryWrite1Byte(ThreadState *thr, uptr pc, uptr addr); @@ -425,21 +544,25 @@ void MemoryAccessRange(ThreadState *thr, uptr pc, uptr addr, uptr size, bool is_write); void MemoryResetRange(ThreadState *thr, uptr pc, uptr addr, uptr size); void MemoryRangeFreed(ThreadState *thr, uptr pc, uptr addr, uptr size); +void MemoryRangeImitateWrite(ThreadState *thr, uptr pc, uptr addr, uptr size); void IgnoreCtl(ThreadState *thr, bool write, bool begin); void FuncEntry(ThreadState *thr, uptr pc); void FuncExit(ThreadState *thr); int ThreadCreate(ThreadState *thr, uptr pc, uptr uid, bool detached); -void ThreadStart(ThreadState *thr, int tid); +void ThreadStart(ThreadState *thr, int tid, uptr os_id); void ThreadFinish(ThreadState *thr); int ThreadTid(ThreadState *thr, uptr pc, uptr uid); void ThreadJoin(ThreadState *thr, uptr pc, int tid); void ThreadDetach(ThreadState *thr, uptr pc, int tid); void ThreadFinalize(ThreadState *thr); -void ThreadFinalizerGoroutine(ThreadState *thr); +void ThreadSetName(ThreadState *thr, const char *name); +int ThreadCount(ThreadState *thr); +void ProcessPendingSignals(ThreadState *thr); -void MutexCreate(ThreadState *thr, uptr pc, uptr addr, bool rw, bool recursive); +void MutexCreate(ThreadState *thr, uptr pc, uptr addr, + bool rw, bool recursive, bool linker_init); void MutexDestroy(ThreadState *thr, uptr pc, uptr addr); void MutexLock(ThreadState *thr, uptr pc, uptr addr); void MutexUnlock(ThreadState *thr, uptr pc, uptr addr); @@ -448,8 +571,10 @@ void MutexReadUnlock(ThreadState *thr, uptr pc, uptr addr); void MutexReadOrWriteUnlock(ThreadState *thr, uptr pc, uptr addr); void Acquire(ThreadState *thr, uptr pc, uptr addr); +void AcquireGlobal(ThreadState *thr, uptr pc); void Release(ThreadState *thr, uptr pc, uptr addr); void ReleaseStore(ThreadState *thr, uptr pc, uptr addr); +void AfterSleep(ThreadState *thr, uptr pc); // The hacky call uses custom calling convention and an assembly thunk. // It is considerably faster that a normal call for the caller @@ -461,27 +586,39 @@ void ReleaseStore(ThreadState *thr, uptr pc, uptr addr); // The caller may not create the stack frame for itself at all, // so we create a reserve stack frame for it (1024b must be enough). #define HACKY_CALL(f) \ - __asm__ __volatile__("sub $0x400, %%rsp;" \ + __asm__ __volatile__("sub $1024, %%rsp;" \ + "/*.cfi_adjust_cfa_offset 1024;*/" \ + ".hidden " #f "_thunk;" \ "call " #f "_thunk;" \ - "add $0x400, %%rsp;" ::: "memory"); + "add $1024, %%rsp;" \ + "/*.cfi_adjust_cfa_offset -1024;*/" \ + ::: "memory", "cc"); #else #define HACKY_CALL(f) f() #endif void TraceSwitch(ThreadState *thr); +uptr TraceTopPC(ThreadState *thr); +uptr TraceSize(); +uptr TraceParts(); extern "C" void __tsan_trace_switch(); -void ALWAYS_INLINE INLINE TraceAddEvent(ThreadState *thr, u64 epoch, - EventType typ, uptr addr) { +void ALWAYS_INLINE INLINE TraceAddEvent(ThreadState *thr, FastState fs, + EventType typ, u64 addr) { + DCHECK_GE((int)typ, 0); + DCHECK_LE((int)typ, 7); + DCHECK_EQ(GetLsb(addr, 61), addr); StatInc(thr, StatEvents); - if (UNLIKELY((epoch % kTracePartSize) == 0)) { + u64 pos = fs.GetTracePos(); + if (UNLIKELY((pos % kTracePartSize) == 0)) { #ifndef TSAN_GO HACKY_CALL(__tsan_trace_switch); #else TraceSwitch(thr); #endif } - Event *evp = &thr->trace.events[epoch % kTraceSize]; + Event *trace = (Event*)GetThreadTrace(fs.tid()); + Event *evp = &trace[pos]; Event ev = (u64)addr | ((u64)typ << 61); *evp = ev; } diff --git a/lib/tsan/rtl/tsan_rtl_amd64.S b/lib/tsan/rtl/tsan_rtl_amd64.S index 2028ec508611..af878563573e 100644 --- a/lib/tsan/rtl/tsan_rtl_amd64.S +++ b/lib/tsan/rtl/tsan_rtl_amd64.S @@ -1,20 +1,43 @@ .section .text +.hidden __tsan_trace_switch .globl __tsan_trace_switch_thunk __tsan_trace_switch_thunk: + .cfi_startproc # Save scratch registers. push %rax + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %rax, 0 push %rcx + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %rcx, 0 push %rdx + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %rdx, 0 push %rsi + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %rsi, 0 push %rdi + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %rdi, 0 push %r8 + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %r8, 0 push %r9 + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %r9, 0 push %r10 + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %r10, 0 push %r11 + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %r11, 0 # Align stack frame. push %rbx # non-scratch + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %rbx, 0 mov %rsp, %rbx # save current rsp + .cfi_def_cfa_register %rbx shr $4, %rsp # clear 4 lsb, align to 16 shl $4, %rsp @@ -22,34 +45,79 @@ __tsan_trace_switch_thunk: # Unalign stack frame back. mov %rbx, %rsp # restore the original rsp + .cfi_def_cfa_register %rsp pop %rbx + .cfi_adjust_cfa_offset -8 # Restore scratch registers. pop %r11 + .cfi_adjust_cfa_offset -8 pop %r10 + .cfi_adjust_cfa_offset -8 pop %r9 + .cfi_adjust_cfa_offset -8 pop %r8 + .cfi_adjust_cfa_offset -8 pop %rdi + .cfi_adjust_cfa_offset -8 pop %rsi + .cfi_adjust_cfa_offset -8 pop %rdx + .cfi_adjust_cfa_offset -8 pop %rcx + .cfi_adjust_cfa_offset -8 pop %rax + .cfi_adjust_cfa_offset -8 + .cfi_restore %rax + .cfi_restore %rbx + .cfi_restore %rcx + .cfi_restore %rdx + .cfi_restore %rsi + .cfi_restore %rdi + .cfi_restore %r8 + .cfi_restore %r9 + .cfi_restore %r10 + .cfi_restore %r11 ret + .cfi_endproc +.hidden __tsan_report_race .globl __tsan_report_race_thunk __tsan_report_race_thunk: + .cfi_startproc # Save scratch registers. push %rax + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %rax, 0 push %rcx + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %rcx, 0 push %rdx + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %rdx, 0 push %rsi + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %rsi, 0 push %rdi + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %rdi, 0 push %r8 + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %r8, 0 push %r9 + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %r9, 0 push %r10 + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %r10, 0 push %r11 + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %r11, 0 # Align stack frame. push %rbx # non-scratch + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %rbx, 0 mov %rsp, %rbx # save current rsp + .cfi_def_cfa_register %rbx shr $4, %rsp # clear 4 lsb, align to 16 shl $4, %rsp @@ -57,15 +125,42 @@ __tsan_report_race_thunk: # Unalign stack frame back. mov %rbx, %rsp # restore the original rsp + .cfi_def_cfa_register %rsp pop %rbx + .cfi_adjust_cfa_offset -8 # Restore scratch registers. pop %r11 + .cfi_adjust_cfa_offset -8 pop %r10 + .cfi_adjust_cfa_offset -8 pop %r9 + .cfi_adjust_cfa_offset -8 pop %r8 + .cfi_adjust_cfa_offset -8 pop %rdi + .cfi_adjust_cfa_offset -8 pop %rsi + .cfi_adjust_cfa_offset -8 pop %rdx + .cfi_adjust_cfa_offset -8 pop %rcx + .cfi_adjust_cfa_offset -8 pop %rax + .cfi_adjust_cfa_offset -8 + .cfi_restore %rax + .cfi_restore %rbx + .cfi_restore %rcx + .cfi_restore %rdx + .cfi_restore %rsi + .cfi_restore %rdi + .cfi_restore %r8 + .cfi_restore %r9 + .cfi_restore %r10 + .cfi_restore %r11 ret + .cfi_endproc + +#ifdef __linux__ +/* We do not need executable stack. */ +.section .note.GNU-stack,"",@progbits +#endif diff --git a/lib/tsan/rtl/tsan_rtl_mutex.cc b/lib/tsan/rtl/tsan_rtl_mutex.cc index 882def835658..d812f12be560 100644 --- a/lib/tsan/rtl/tsan_rtl_mutex.cc +++ b/lib/tsan/rtl/tsan_rtl_mutex.cc @@ -12,22 +12,26 @@ //===----------------------------------------------------------------------===// #include "tsan_rtl.h" +#include "tsan_flags.h" #include "tsan_sync.h" #include "tsan_report.h" #include "tsan_symbolize.h" +#include "tsan_platform.h" namespace __tsan { void MutexCreate(ThreadState *thr, uptr pc, uptr addr, - bool rw, bool recursive) { + bool rw, bool recursive, bool linker_init) { Context *ctx = CTX(); CHECK_GT(thr->in_rtl, 0); DPrintf("#%d: MutexCreate %zx\n", thr->tid, addr); StatInc(thr, StatMutexCreate); - MemoryWrite1Byte(thr, pc, addr); - SyncVar *s = ctx->synctab.GetAndLock(thr, pc, addr, true); + if (!linker_init && IsAppMem(addr)) + MemoryWrite1Byte(thr, pc, addr); + SyncVar *s = ctx->synctab.GetOrCreateAndLock(thr, pc, addr, true); s->is_rw = rw; s->is_recursive = recursive; + s->is_linker_init = linker_init; s->mtx.Unlock(); } @@ -36,34 +40,54 @@ void MutexDestroy(ThreadState *thr, uptr pc, uptr addr) { CHECK_GT(thr->in_rtl, 0); DPrintf("#%d: MutexDestroy %zx\n", thr->tid, addr); StatInc(thr, StatMutexDestroy); - MemoryWrite1Byte(thr, pc, addr); +#ifndef TSAN_GO + // Global mutexes not marked as LINKER_INITIALIZED + // cause tons of not interesting reports, so just ignore it. + if (IsGlobalVar(addr)) + return; +#endif SyncVar *s = ctx->synctab.GetAndRemove(thr, pc, addr); if (s == 0) return; - if (s->owner_tid != SyncVar::kInvalidTid && !s->is_broken) { + if (IsAppMem(addr)) + MemoryWrite1Byte(thr, pc, addr); + if (flags()->report_destroy_locked + && s->owner_tid != SyncVar::kInvalidTid + && !s->is_broken) { s->is_broken = true; + Lock l(&ctx->thread_mtx); ScopedReport rep(ReportTypeMutexDestroyLocked); rep.AddMutex(s); + StackTrace trace; + trace.ObtainCurrent(thr, pc); + rep.AddStack(&trace); + FastState last(s->last_lock); + RestoreStack(last.tid(), last.epoch(), &trace, 0); + rep.AddStack(&trace); rep.AddLocation(s->addr, 1); - OutputReport(rep); + OutputReport(ctx, rep); } + thr->mset.Remove(s->GetId()); DestroyAndFree(s); } void MutexLock(ThreadState *thr, uptr pc, uptr addr) { CHECK_GT(thr->in_rtl, 0); DPrintf("#%d: MutexLock %zx\n", thr->tid, addr); - MemoryRead1Byte(thr, pc, addr); + if (IsAppMem(addr)) + MemoryRead1Byte(thr, pc, addr); + SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, addr, true); thr->fast_state.IncrementEpoch(); - TraceAddEvent(thr, thr->fast_state.epoch(), EventTypeLock, addr); - SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, true); + TraceAddEvent(thr, thr->fast_state, EventTypeLock, s->GetId()); if (s->owner_tid == SyncVar::kInvalidTid) { CHECK_EQ(s->recursion, 0); s->owner_tid = thr->tid; + s->last_lock = thr->fast_state.raw(); } else if (s->owner_tid == thr->tid) { CHECK_GT(s->recursion, 0); } else { - TsanPrintf("ThreadSanitizer WARNING: double lock\n"); + Printf("ThreadSanitizer WARNING: double lock\n"); + PrintCurrentStack(thr, pc); } if (s->recursion == 0) { StatInc(thr, StatMutexLock); @@ -76,25 +100,29 @@ void MutexLock(ThreadState *thr, uptr pc, uptr addr) { StatInc(thr, StatMutexRecLock); } s->recursion++; + thr->mset.Add(s->GetId(), true, thr->fast_state.epoch()); s->mtx.Unlock(); } void MutexUnlock(ThreadState *thr, uptr pc, uptr addr) { CHECK_GT(thr->in_rtl, 0); DPrintf("#%d: MutexUnlock %zx\n", thr->tid, addr); - MemoryRead1Byte(thr, pc, addr); + if (IsAppMem(addr)) + MemoryRead1Byte(thr, pc, addr); + SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, addr, true); thr->fast_state.IncrementEpoch(); - TraceAddEvent(thr, thr->fast_state.epoch(), EventTypeUnlock, addr); - SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, true); + TraceAddEvent(thr, thr->fast_state, EventTypeUnlock, s->GetId()); if (s->recursion == 0) { if (!s->is_broken) { s->is_broken = true; - TsanPrintf("ThreadSanitizer WARNING: unlock of unlocked mutex\n"); + Printf("ThreadSanitizer WARNING: unlock of unlocked mutex\n"); + PrintCurrentStack(thr, pc); } } else if (s->owner_tid != thr->tid) { if (!s->is_broken) { s->is_broken = true; - TsanPrintf("ThreadSanitizer WARNING: mutex unlock by another thread\n"); + Printf("ThreadSanitizer WARNING: mutex unlock by another thread\n"); + PrintCurrentStack(thr, pc); } } else { s->recursion--; @@ -103,12 +131,13 @@ void MutexUnlock(ThreadState *thr, uptr pc, uptr addr) { s->owner_tid = SyncVar::kInvalidTid; thr->clock.set(thr->tid, thr->fast_state.epoch()); thr->fast_synch_epoch = thr->fast_state.epoch(); - thr->clock.release(&s->clock); + thr->clock.ReleaseStore(&s->clock); StatInc(thr, StatSyncRelease); } else { StatInc(thr, StatMutexRecUnlock); } } + thr->mset.Del(s->GetId(), true); s->mtx.Unlock(); } @@ -116,15 +145,20 @@ void MutexReadLock(ThreadState *thr, uptr pc, uptr addr) { CHECK_GT(thr->in_rtl, 0); DPrintf("#%d: MutexReadLock %zx\n", thr->tid, addr); StatInc(thr, StatMutexReadLock); - MemoryRead1Byte(thr, pc, addr); + if (IsAppMem(addr)) + MemoryRead1Byte(thr, pc, addr); + SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, addr, false); thr->fast_state.IncrementEpoch(); - TraceAddEvent(thr, thr->fast_state.epoch(), EventTypeRLock, addr); - SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, false); - if (s->owner_tid != SyncVar::kInvalidTid) - TsanPrintf("ThreadSanitizer WARNING: read lock of a write locked mutex\n"); + TraceAddEvent(thr, thr->fast_state, EventTypeRLock, s->GetId()); + if (s->owner_tid != SyncVar::kInvalidTid) { + Printf("ThreadSanitizer WARNING: read lock of a write locked mutex\n"); + PrintCurrentStack(thr, pc); + } thr->clock.set(thr->tid, thr->fast_state.epoch()); thr->clock.acquire(&s->clock); + s->last_lock = thr->fast_state.raw(); StatInc(thr, StatSyncAcquire); + thr->mset.Add(s->GetId(), false, thr->fast_state.epoch()); s->mtx.ReadUnlock(); } @@ -132,36 +166,45 @@ void MutexReadUnlock(ThreadState *thr, uptr pc, uptr addr) { CHECK_GT(thr->in_rtl, 0); DPrintf("#%d: MutexReadUnlock %zx\n", thr->tid, addr); StatInc(thr, StatMutexReadUnlock); - MemoryRead1Byte(thr, pc, addr); + if (IsAppMem(addr)) + MemoryRead1Byte(thr, pc, addr); + SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, addr, true); thr->fast_state.IncrementEpoch(); - TraceAddEvent(thr, thr->fast_state.epoch(), EventTypeRUnlock, addr); - SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, true); - if (s->owner_tid != SyncVar::kInvalidTid) - TsanPrintf("ThreadSanitizer WARNING: read unlock of a write " + TraceAddEvent(thr, thr->fast_state, EventTypeRUnlock, s->GetId()); + if (s->owner_tid != SyncVar::kInvalidTid) { + Printf("ThreadSanitizer WARNING: read unlock of a write " "locked mutex\n"); + PrintCurrentStack(thr, pc); + } thr->clock.set(thr->tid, thr->fast_state.epoch()); thr->fast_synch_epoch = thr->fast_state.epoch(); thr->clock.release(&s->read_clock); StatInc(thr, StatSyncRelease); s->mtx.Unlock(); + thr->mset.Del(s->GetId(), false); } void MutexReadOrWriteUnlock(ThreadState *thr, uptr pc, uptr addr) { CHECK_GT(thr->in_rtl, 0); DPrintf("#%d: MutexReadOrWriteUnlock %zx\n", thr->tid, addr); - MemoryRead1Byte(thr, pc, addr); - SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, true); + if (IsAppMem(addr)) + MemoryRead1Byte(thr, pc, addr); + SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, addr, true); + bool write = true; if (s->owner_tid == SyncVar::kInvalidTid) { // Seems to be read unlock. + write = false; StatInc(thr, StatMutexReadUnlock); thr->fast_state.IncrementEpoch(); - TraceAddEvent(thr, thr->fast_state.epoch(), EventTypeRUnlock, addr); + TraceAddEvent(thr, thr->fast_state, EventTypeRUnlock, s->GetId()); thr->clock.set(thr->tid, thr->fast_state.epoch()); thr->fast_synch_epoch = thr->fast_state.epoch(); thr->clock.release(&s->read_clock); StatInc(thr, StatSyncRelease); } else if (s->owner_tid == thr->tid) { // Seems to be write unlock. + thr->fast_state.IncrementEpoch(); + TraceAddEvent(thr, thr->fast_state, EventTypeUnlock, s->GetId()); CHECK_GT(s->recursion, 0); s->recursion--; if (s->recursion == 0) { @@ -171,36 +214,50 @@ void MutexReadOrWriteUnlock(ThreadState *thr, uptr pc, uptr addr) { // The sequence of events is quite tricky and doubled in several places. // First, it's a bug to increment the epoch w/o writing to the trace. // Then, the acquire/release logic can be factored out as well. - thr->fast_state.IncrementEpoch(); - TraceAddEvent(thr, thr->fast_state.epoch(), EventTypeUnlock, addr); thr->clock.set(thr->tid, thr->fast_state.epoch()); thr->fast_synch_epoch = thr->fast_state.epoch(); - thr->clock.release(&s->clock); + thr->clock.ReleaseStore(&s->clock); StatInc(thr, StatSyncRelease); } else { StatInc(thr, StatMutexRecUnlock); } } else if (!s->is_broken) { s->is_broken = true; - TsanPrintf("ThreadSanitizer WARNING: mutex unlock by another thread\n"); + Printf("ThreadSanitizer WARNING: mutex unlock by another thread\n"); + PrintCurrentStack(thr, pc); } + thr->mset.Del(s->GetId(), write); s->mtx.Unlock(); } void Acquire(ThreadState *thr, uptr pc, uptr addr) { CHECK_GT(thr->in_rtl, 0); DPrintf("#%d: Acquire %zx\n", thr->tid, addr); - SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, false); + SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, addr, false); thr->clock.set(thr->tid, thr->fast_state.epoch()); thr->clock.acquire(&s->clock); StatInc(thr, StatSyncAcquire); s->mtx.ReadUnlock(); } +void AcquireGlobal(ThreadState *thr, uptr pc) { + Context *ctx = CTX(); + Lock l(&ctx->thread_mtx); + for (unsigned i = 0; i < kMaxTid; i++) { + ThreadContext *tctx = ctx->threads[i]; + if (tctx == 0) + continue; + if (tctx->status == ThreadStatusRunning) + thr->clock.set(i, tctx->thr->fast_state.epoch()); + else + thr->clock.set(i, tctx->epoch1); + } +} + void Release(ThreadState *thr, uptr pc, uptr addr) { CHECK_GT(thr->in_rtl, 0); DPrintf("#%d: Release %zx\n", thr->tid, addr); - SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, true); + SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, addr, true); thr->clock.set(thr->tid, thr->fast_state.epoch()); thr->clock.release(&s->clock); StatInc(thr, StatSyncRelease); @@ -210,11 +267,28 @@ void Release(ThreadState *thr, uptr pc, uptr addr) { void ReleaseStore(ThreadState *thr, uptr pc, uptr addr) { CHECK_GT(thr->in_rtl, 0); DPrintf("#%d: ReleaseStore %zx\n", thr->tid, addr); - SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, true); + SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, addr, true); thr->clock.set(thr->tid, thr->fast_state.epoch()); thr->clock.ReleaseStore(&s->clock); StatInc(thr, StatSyncRelease); s->mtx.Unlock(); } +#ifndef TSAN_GO +void AfterSleep(ThreadState *thr, uptr pc) { + Context *ctx = CTX(); + thr->last_sleep_stack_id = CurrentStackId(thr, pc); + Lock l(&ctx->thread_mtx); + for (unsigned i = 0; i < kMaxTid; i++) { + ThreadContext *tctx = ctx->threads[i]; + if (tctx == 0) + continue; + if (tctx->status == ThreadStatusRunning) + thr->last_sleep_clock.set(i, tctx->thr->fast_state.epoch()); + else + thr->last_sleep_clock.set(i, tctx->epoch1); + } +} +#endif + } // namespace __tsan diff --git a/lib/tsan/rtl/tsan_rtl_report.cc b/lib/tsan/rtl/tsan_rtl_report.cc index f66e17e4815c..1a780e4b8070 100644 --- a/lib/tsan/rtl/tsan_rtl_report.cc +++ b/lib/tsan/rtl/tsan_rtl_report.cc @@ -13,6 +13,8 @@ #include "sanitizer_common/sanitizer_libc.h" #include "sanitizer_common/sanitizer_placement_new.h" +#include "sanitizer_common/sanitizer_stackdepot.h" +#include "sanitizer_common/sanitizer_common.h" #include "tsan_platform.h" #include "tsan_rtl.h" #include "tsan_suppressions.h" @@ -21,26 +23,26 @@ #include "tsan_sync.h" #include "tsan_mman.h" #include "tsan_flags.h" +#include "tsan_fd.h" -namespace __sanitizer { -using namespace __tsan; +namespace __tsan { + +using namespace __sanitizer; // NOLINT -void CheckFailed(const char *file, int line, const char *cond, u64 v1, u64 v2) { +void TsanCheckFailed(const char *file, int line, const char *cond, + u64 v1, u64 v2) { ScopedInRtl in_rtl; - TsanPrintf("FATAL: ThreadSanitizer CHECK failed: " - "%s:%d \"%s\" (0x%zx, 0x%zx)\n", - file, line, cond, (uptr)v1, (uptr)v2); + Printf("FATAL: ThreadSanitizer CHECK failed: " + "%s:%d \"%s\" (0x%zx, 0x%zx)\n", + file, line, cond, (uptr)v1, (uptr)v2); Die(); } -} // namespace __sanitizer - -namespace __tsan { - // Can be overriden by an application/test to intercept reports. #ifdef TSAN_EXTERNAL_HOOKS bool OnReport(const ReportDesc *rep, bool suppressed); #else +SANITIZER_INTERFACE_ATTRIBUTE bool WEAK OnReport(const ReportDesc *rep, bool suppressed) { (void)rep; return suppressed; @@ -84,9 +86,9 @@ static void StackStripMain(ReportStack *stack) { } else if (last || last2) { // Ensure that we recovered stack completely. Trimmed stack // can actually happen if we do not instrument some code, - // so it's only a DCHECK. However we must try hard to not miss it + // so it's only a debug print. However we must try hard to not miss it // due to our fault. - TsanPrintf("Bottom stack frame of stack %zx is missed\n", stack->pc); + DPrintf("Bottom stack frame of stack %zx is missed\n", stack->pc); } #else if (last && 0 == internal_strcmp(last, "schedunlock")) @@ -119,6 +121,7 @@ static ReportStack *SymbolizeStack(const StackTrace& trace) { ScopedReport::ScopedReport(ReportType typ) { ctx_ = CTX(); + ctx_->thread_mtx.CheckLocked(); void *mem = internal_alloc(MBlockReport, sizeof(ReportDesc)); rep_ = new(mem) ReportDesc; rep_->typ = typ; @@ -127,8 +130,7 @@ ScopedReport::ScopedReport(ReportType typ) { ScopedReport::~ScopedReport() { ctx_->report_mtx.Unlock(); - rep_->~ReportDesc(); - internal_free(rep_); + DestroyAndFree(rep_); } void ScopedReport::AddStack(const StackTrace *stack) { @@ -137,7 +139,7 @@ void ScopedReport::AddStack(const StackTrace *stack) { } void ScopedReport::AddMemoryAccess(uptr addr, Shadow s, - const StackTrace *stack) { + const StackTrace *stack, const MutexSet *mset) { void *mem = internal_alloc(MBlockReportMop, sizeof(ReportMop)); ReportMop *mop = new(mem) ReportMop; rep_->mops.PushBack(mop); @@ -145,50 +147,195 @@ void ScopedReport::AddMemoryAccess(uptr addr, Shadow s, mop->addr = addr + s.addr0(); mop->size = s.size(); mop->write = s.is_write(); - mop->nmutex = 0; mop->stack = SymbolizeStack(*stack); + for (uptr i = 0; i < mset->Size(); i++) { + MutexSet::Desc d = mset->Get(i); + u64 uid = 0; + uptr addr = SyncVar::SplitId(d.id, &uid); + SyncVar *s = ctx_->synctab.GetIfExistsAndLock(addr, false); + // Check that the mutex is still alive. + // Another mutex can be created at the same address, + // so check uid as well. + if (s && s->CheckId(uid)) { + ReportMopMutex mtx = {s->uid, d.write}; + mop->mset.PushBack(mtx); + AddMutex(s); + } else { + ReportMopMutex mtx = {d.id, d.write}; + mop->mset.PushBack(mtx); + AddMutex(d.id); + } + if (s) + s->mtx.ReadUnlock(); + } } void ScopedReport::AddThread(const ThreadContext *tctx) { + for (uptr i = 0; i < rep_->threads.Size(); i++) { + if (rep_->threads[i]->id == tctx->tid) + return; + } void *mem = internal_alloc(MBlockReportThread, sizeof(ReportThread)); ReportThread *rt = new(mem) ReportThread(); rep_->threads.PushBack(rt); rt->id = tctx->tid; + rt->pid = tctx->os_id; rt->running = (tctx->status == ThreadStatusRunning); + rt->name = tctx->name ? internal_strdup(tctx->name) : 0; + rt->parent_tid = tctx->creation_tid; rt->stack = SymbolizeStack(tctx->creation_stack); } +#ifndef TSAN_GO +static ThreadContext *FindThread(int unique_id) { + Context *ctx = CTX(); + ctx->thread_mtx.CheckLocked(); + for (unsigned i = 0; i < kMaxTid; i++) { + ThreadContext *tctx = ctx->threads[i]; + if (tctx && tctx->unique_id == unique_id) { + return tctx; + } + } + return 0; +} + +ThreadContext *IsThreadStackOrTls(uptr addr, bool *is_stack) { + Context *ctx = CTX(); + ctx->thread_mtx.CheckLocked(); + for (unsigned i = 0; i < kMaxTid; i++) { + ThreadContext *tctx = ctx->threads[i]; + if (tctx == 0 || tctx->status != ThreadStatusRunning) + continue; + ThreadState *thr = tctx->thr; + CHECK(thr); + if (addr >= thr->stk_addr && addr < thr->stk_addr + thr->stk_size) { + *is_stack = true; + return tctx; + } + if (addr >= thr->tls_addr && addr < thr->tls_addr + thr->tls_size) { + *is_stack = false; + return tctx; + } + } + return 0; +} +#endif + void ScopedReport::AddMutex(const SyncVar *s) { + for (uptr i = 0; i < rep_->mutexes.Size(); i++) { + if (rep_->mutexes[i]->id == s->uid) + return; + } void *mem = internal_alloc(MBlockReportMutex, sizeof(ReportMutex)); ReportMutex *rm = new(mem) ReportMutex(); rep_->mutexes.PushBack(rm); - rm->id = 42; + rm->id = s->uid; + rm->destroyed = false; rm->stack = SymbolizeStack(s->creation_stack); } +void ScopedReport::AddMutex(u64 id) { + for (uptr i = 0; i < rep_->mutexes.Size(); i++) { + if (rep_->mutexes[i]->id == id) + return; + } + void *mem = internal_alloc(MBlockReportMutex, sizeof(ReportMutex)); + ReportMutex *rm = new(mem) ReportMutex(); + rep_->mutexes.PushBack(rm); + rm->id = id; + rm->destroyed = true; + rm->stack = 0; +} + void ScopedReport::AddLocation(uptr addr, uptr size) { - ReportStack *symb = SymbolizeData(addr); - if (symb) { + if (addr == 0) + return; +#ifndef TSAN_GO + int fd = -1; + int creat_tid = -1; + u32 creat_stack = 0; + if (FdLocation(addr, &fd, &creat_tid, &creat_stack) + || FdLocation(AlternativeAddress(addr), &fd, &creat_tid, &creat_stack)) { + void *mem = internal_alloc(MBlockReportLoc, sizeof(ReportLocation)); + ReportLocation *loc = new(mem) ReportLocation(); + rep_->locs.PushBack(loc); + loc->type = ReportLocationFD; + loc->fd = fd; + loc->tid = creat_tid; + uptr ssz = 0; + const uptr *stack = StackDepotGet(creat_stack, &ssz); + if (stack) { + StackTrace trace; + trace.Init(stack, ssz); + loc->stack = SymbolizeStack(trace); + } + ThreadContext *tctx = FindThread(creat_tid); + if (tctx) + AddThread(tctx); + return; + } + if (allocator()->PointerIsMine((void*)addr)) { + MBlock *b = user_mblock(0, (void*)addr); + ThreadContext *tctx = FindThread(b->alloc_tid); void *mem = internal_alloc(MBlockReportLoc, sizeof(ReportLocation)); ReportLocation *loc = new(mem) ReportLocation(); rep_->locs.PushBack(loc); - loc->type = ReportLocationGlobal; - loc->addr = addr; - loc->size = size; - loc->tid = 0; - loc->name = symb->func; - loc->file = symb->file; - loc->line = symb->line; + loc->type = ReportLocationHeap; + loc->addr = (uptr)allocator()->GetBlockBegin((void*)addr); + loc->size = b->size; + loc->tid = tctx ? tctx->tid : b->alloc_tid; + loc->name = 0; + loc->file = 0; + loc->line = 0; loc->stack = 0; - internal_free(symb); + uptr ssz = 0; + const uptr *stack = StackDepotGet(b->alloc_stack_id, &ssz); + if (stack) { + StackTrace trace; + trace.Init(stack, ssz); + loc->stack = SymbolizeStack(trace); + } + if (tctx) + AddThread(tctx); + return; } + bool is_stack = false; + if (ThreadContext *tctx = IsThreadStackOrTls(addr, &is_stack)) { + void *mem = internal_alloc(MBlockReportLoc, sizeof(ReportLocation)); + ReportLocation *loc = new(mem) ReportLocation(); + rep_->locs.PushBack(loc); + loc->type = is_stack ? ReportLocationStack : ReportLocationTLS; + loc->tid = tctx->tid; + AddThread(tctx); + } + ReportLocation *loc = SymbolizeData(addr); + if (loc) { + rep_->locs.PushBack(loc); + return; + } +#endif } +#ifndef TSAN_GO +void ScopedReport::AddSleep(u32 stack_id) { + uptr ssz = 0; + const uptr *stack = StackDepotGet(stack_id, &ssz); + if (stack) { + StackTrace trace; + trace.Init(stack, ssz); + rep_->sleep = SymbolizeStack(trace); + } +} +#endif + const ReportDesc *ScopedReport::GetReport() const { return rep_; } -static void RestoreStack(int tid, const u64 epoch, StackTrace *stk) { +void RestoreStack(int tid, const u64 epoch, StackTrace *stk, MutexSet *mset) { + // This function restores stack trace and mutex set for the thread/epoch. + // It does so by getting stack trace and mutex set at the beginning of + // trace part, and then replaying the trace till the given epoch. ThreadContext *tctx = CTX()->threads[tid]; if (tctx == 0) return; @@ -205,49 +352,62 @@ static void RestoreStack(int tid, const u64 epoch, StackTrace *stk) { return; } Lock l(&trace->mtx); - const int partidx = (epoch / (kTraceSize / kTraceParts)) % kTraceParts; + const int partidx = (epoch / kTracePartSize) % TraceParts(); TraceHeader* hdr = &trace->headers[partidx]; if (epoch < hdr->epoch0) return; - const u64 eend = epoch % kTraceSize; - const u64 ebegin = eend / kTracePartSize * kTracePartSize; + const u64 epoch0 = RoundDown(epoch, TraceSize()); + const u64 eend = epoch % TraceSize(); + const u64 ebegin = RoundDown(eend, kTracePartSize); DPrintf("#%d: RestoreStack epoch=%zu ebegin=%zu eend=%zu partidx=%d\n", tid, (uptr)epoch, (uptr)ebegin, (uptr)eend, partidx); - InternalScopedBuf<uptr> stack(1024); // FIXME: de-hardcode 1024 + InternalScopedBuffer<uptr> stack(1024); // FIXME: de-hardcode 1024 for (uptr i = 0; i < hdr->stack0.Size(); i++) { stack[i] = hdr->stack0.Get(i); DPrintf2(" #%02lu: pc=%zx\n", i, stack[i]); } + if (mset) + *mset = hdr->mset0; uptr pos = hdr->stack0.Size(); + Event *events = (Event*)GetThreadTrace(tid); for (uptr i = ebegin; i <= eend; i++) { - Event ev = trace->events[i]; + Event ev = events[i]; EventType typ = (EventType)(ev >> 61); - uptr pc = (uptr)(ev & 0xffffffffffffull); + uptr pc = (uptr)(ev & ((1ull << 61) - 1)); DPrintf2(" %zu typ=%d pc=%zx\n", i, typ, pc); if (typ == EventTypeMop) { stack[pos] = pc; } else if (typ == EventTypeFuncEnter) { stack[pos++] = pc; } else if (typ == EventTypeFuncExit) { - // Since we have full stacks, this should never happen. - DCHECK_GT(pos, 0); if (pos > 0) pos--; } + if (mset) { + if (typ == EventTypeLock) { + mset->Add(pc, true, epoch0 + i); + } else if (typ == EventTypeUnlock) { + mset->Del(pc, true); + } else if (typ == EventTypeRLock) { + mset->Add(pc, false, epoch0 + i); + } else if (typ == EventTypeRUnlock) { + mset->Del(pc, false); + } + } for (uptr j = 0; j <= pos; j++) DPrintf2(" #%zu: %zx\n", j, stack[j]); } if (pos == 0 && stack[0] == 0) return; pos++; - stk->Init(stack, pos); + stk->Init(stack.data(), pos); } static bool HandleRacyStacks(ThreadState *thr, const StackTrace (&traces)[2], uptr addr_min, uptr addr_max) { Context *ctx = CTX(); bool equal_stack = false; - RacyStacks hash = {}; + RacyStacks hash; if (flags()->suppress_equal_stacks) { hash.hash[0] = md5_hash(traces[0].Begin(), traces[0].Size() * sizeof(uptr)); hash.hash[1] = md5_hash(traces[1].Begin(), traces[1].Size() * sizeof(uptr)); @@ -298,20 +458,81 @@ static void AddRacyStacks(ThreadState *thr, const StackTrace (&traces)[2], } } -bool OutputReport(const ScopedReport &srep, const ReportStack *suppress_stack) { +bool OutputReport(Context *ctx, + const ScopedReport &srep, + const ReportStack *suppress_stack) { const ReportDesc *rep = srep.GetReport(); - bool suppressed = IsSuppressed(rep->typ, suppress_stack); - suppressed = OnReport(rep, suppressed); - if (suppressed) + const uptr suppress_pc = IsSuppressed(rep->typ, suppress_stack); + if (suppress_pc != 0) { + FiredSuppression supp = {srep.GetReport()->typ, suppress_pc}; + ctx->fired_suppressions.PushBack(supp); + } + if (OnReport(rep, suppress_pc != 0)) return false; PrintReport(rep); CTX()->nreported++; return true; } +bool IsFiredSuppression(Context *ctx, + const ScopedReport &srep, + const StackTrace &trace) { + for (uptr k = 0; k < ctx->fired_suppressions.Size(); k++) { + if (ctx->fired_suppressions[k].type != srep.GetReport()->typ) + continue; + for (uptr j = 0; j < trace.Size(); j++) { + if (trace.Get(j) == ctx->fired_suppressions[k].pc) + return true; + } + } + return false; +} + +// On programs that use Java we see weird reports like: +// WARNING: ThreadSanitizer: data race (pid=22512) +// Read of size 8 at 0x7d2b00084318 by thread 100: +// #0 memcpy tsan_interceptors.cc:406 (foo+0x00000d8dfae3) +// #1 <null> <null>:0 (0x7f7ad9b40193) +// Previous write of size 8 at 0x7d2b00084318 by thread 105: +// #0 strncpy tsan_interceptors.cc:501 (foo+0x00000d8e0919) +// #1 <null> <null>:0 (0x7f7ad9b42707) +static bool IsJavaNonsense(const ReportDesc *rep) { + for (uptr i = 0; i < rep->mops.Size(); i++) { + ReportMop *mop = rep->mops[i]; + ReportStack *frame = mop->stack; + if (frame != 0 && frame->func != 0 + && (internal_strcmp(frame->func, "memset") == 0 + || internal_strcmp(frame->func, "memcpy") == 0 + || internal_strcmp(frame->func, "memmove") == 0 + || internal_strcmp(frame->func, "strcmp") == 0 + || internal_strcmp(frame->func, "strncpy") == 0 + || internal_strcmp(frame->func, "strlen") == 0 + || internal_strcmp(frame->func, "free") == 0 + || internal_strcmp(frame->func, "pthread_mutex_lock") == 0)) { + frame = frame->next; + if (frame == 0 + || (frame->func == 0 && frame->file == 0 && frame->line == 0 + && frame->module == 0)) { + if (frame) { + FiredSuppression supp = {rep->typ, frame->pc}; + CTX()->fired_suppressions.PushBack(supp); + } + return true; + } + } + } + return false; +} + void ReportRace(ThreadState *thr) { + if (!flags()->report_bugs) + return; ScopedInRtl in_rtl; + if (thr->in_signal_handler) + Printf("ThreadSanitizer: printing report from signal handler." + " Can crash or hang.\n"); + bool freed = false; { Shadow s(thr->racy_state[1]); @@ -339,21 +560,26 @@ void ReportRace(ThreadState *thr) { ScopedReport rep(freed ? ReportTypeUseAfterFree : ReportTypeRace); const uptr kMop = 2; StackTrace traces[kMop]; - for (uptr i = 0; i < kMop; i++) { - Shadow s(thr->racy_state[i]); - RestoreStack(s.tid(), s.epoch(), &traces[i]); - } + const uptr toppc = TraceTopPC(thr); + traces[0].ObtainCurrent(thr, toppc); + if (IsFiredSuppression(ctx, rep, traces[0])) + return; + InternalScopedBuffer<MutexSet> mset2(1); + new(mset2.data()) MutexSet(); + Shadow s2(thr->racy_state[1]); + RestoreStack(s2.tid(), s2.epoch(), &traces[1], mset2.data()); if (HandleRacyStacks(thr, traces, addr_min, addr_max)) return; for (uptr i = 0; i < kMop; i++) { Shadow s(thr->racy_state[i]); - rep.AddMemoryAccess(addr, s, &traces[i]); + rep.AddMemoryAccess(addr, s, &traces[i], + i == 0 ? &thr->mset : mset2.data()); } - // Ensure that we have at least something for the current thread. - CHECK_EQ(traces[0].IsEmpty(), false); + if (flags()->suppress_java && IsJavaNonsense(rep.GetReport())) + return; for (uptr i = 0; i < kMop; i++) { FastState s(thr->racy_state[i]); @@ -363,10 +589,26 @@ void ReportRace(ThreadState *thr) { rep.AddThread(tctx); } - if (!OutputReport(rep, rep.GetReport()->mops[0]->stack)) + rep.AddLocation(addr_min, addr_max - addr_min); + +#ifndef TSAN_GO + { // NOLINT + Shadow s(thr->racy_state[1]); + if (s.epoch() <= thr->last_sleep_clock.get(s.tid())) + rep.AddSleep(thr->last_sleep_stack_id); + } +#endif + + if (!OutputReport(ctx, rep, rep.GetReport()->mops[0]->stack)) return; AddRacyStacks(thr, traces, addr_min, addr_max); } +void PrintCurrentStack(ThreadState *thr, uptr pc) { + StackTrace trace; + trace.ObtainCurrent(thr, pc); + PrintStack(SymbolizeStack(trace)); +} + } // namespace __tsan diff --git a/lib/tsan/rtl/tsan_rtl_thread.cc b/lib/tsan/rtl/tsan_rtl_thread.cc index f7d5f13dca78..359775927834 100644 --- a/lib/tsan/rtl/tsan_rtl_thread.cc +++ b/lib/tsan/rtl/tsan_rtl_thread.cc @@ -35,7 +35,7 @@ static void MaybeReportThreadLeak(ThreadContext *tctx) { return; ScopedReport rep(ReportTypeThreadLeak); rep.AddThread(tctx); - OutputReport(rep); + OutputReport(CTX(), rep); } void ThreadFinalize(ThreadState *thr) { @@ -52,6 +52,23 @@ void ThreadFinalize(ThreadState *thr) { } } +int ThreadCount(ThreadState *thr) { + CHECK_GT(thr->in_rtl, 0); + Context *ctx = CTX(); + Lock l(&ctx->thread_mtx); + int cnt = 0; + for (unsigned i = 0; i < kMaxTid; i++) { + ThreadContext *tctx = ctx->threads[i]; + if (tctx == 0) + continue; + if (tctx->status != ThreadStatusCreated + && tctx->status != ThreadStatusRunning) + continue; + cnt++; + } + return cnt; +} + static void ThreadDead(ThreadState *thr, ThreadContext *tctx) { Context *ctx = CTX(); CHECK_GT(thr->in_rtl, 0); @@ -81,8 +98,9 @@ int ThreadCreate(ThreadState *thr, uptr pc, uptr uid, bool detached) { ThreadContext *tctx = 0; if (ctx->dead_list_size > kThreadQuarantineSize || ctx->thread_seq >= kMaxTid) { + // Reusing old thread descriptor and tid. if (ctx->dead_list_size == 0) { - TsanPrintf("ThreadSanitizer: %d thread limit exceeded. Dying.\n", + Printf("ThreadSanitizer: %d thread limit exceeded. Dying.\n", kMaxTid); Die(); } @@ -100,12 +118,18 @@ int ThreadCreate(ThreadState *thr, uptr pc, uptr uid, bool detached) { tctx->sync.Reset(); tid = tctx->tid; DestroyAndFree(tctx->dead_info); + if (tctx->name) { + internal_free(tctx->name); + tctx->name = 0; + } } else { + // Allocating new thread descriptor and tid. StatInc(thr, StatThreadMaxTid); tid = ctx->thread_seq++; void *mem = internal_alloc(MBlockThreadContex, sizeof(ThreadContext)); tctx = new(mem) ThreadContext(tid); ctx->threads[tid] = tctx; + MapThreadTrace(GetThreadTrace(tid), TraceSize() * sizeof(Event)); } CHECK_NE(tctx, 0); CHECK_GE(tid, 0); @@ -126,18 +150,18 @@ int ThreadCreate(ThreadState *thr, uptr pc, uptr uid, bool detached) { if (tid) { thr->fast_state.IncrementEpoch(); // Can't increment epoch w/o writing to the trace as well. - TraceAddEvent(thr, thr->fast_state.epoch(), EventTypeMop, 0); + TraceAddEvent(thr, thr->fast_state, EventTypeMop, 0); thr->clock.set(thr->tid, thr->fast_state.epoch()); thr->fast_synch_epoch = thr->fast_state.epoch(); thr->clock.release(&tctx->sync); StatInc(thr, StatSyncRelease); - tctx->creation_stack.ObtainCurrent(thr, pc); + tctx->creation_tid = thr->tid; } return tid; } -void ThreadStart(ThreadState *thr, int tid) { +void ThreadStart(ThreadState *thr, int tid, uptr os_id) { CHECK_GT(thr->in_rtl, 0); uptr stk_addr = 0; uptr stk_size = 0; @@ -169,10 +193,14 @@ void ThreadStart(ThreadState *thr, int tid) { CHECK_NE(tctx, 0); CHECK_EQ(tctx->status, ThreadStatusCreated); tctx->status = ThreadStatusRunning; - tctx->epoch0 = tctx->epoch1 + 1; + tctx->os_id = os_id; + // RoundUp so that one trace part does not contain events + // from different threads. + tctx->epoch0 = RoundUp(tctx->epoch1 + 1, kTracePartSize); tctx->epoch1 = (u64)-1; - new(thr) ThreadState(CTX(), tid, tctx->epoch0, stk_addr, stk_size, - tls_addr, tls_size); + new(thr) ThreadState(CTX(), tid, tctx->unique_id, + tctx->epoch0, stk_addr, stk_size, + tls_addr, tls_size); #ifdef TSAN_GO // Setup dynamic shadow stack. const int kInitStackSize = 8; @@ -185,6 +213,9 @@ void ThreadStart(ThreadState *thr, int tid) { thr->fast_synch_epoch = tctx->epoch0; thr->clock.set(tid, tctx->epoch0); thr->clock.acquire(&tctx->sync); + thr->fast_state.SetHistorySize(flags()->history_size); + const uptr trace = (tctx->epoch0 / kTracePartSize) % TraceParts(); + thr->trace.headers[trace].epoch0 = tctx->epoch0; StatInc(thr, StatSyncAcquire); DPrintf("#%d: ThreadStart epoch=%zu stk_addr=%zx stk_size=%zx " "tls_addr=%zx tls_size=%zx\n", @@ -219,7 +250,7 @@ void ThreadFinish(ThreadState *thr) { } else { thr->fast_state.IncrementEpoch(); // Can't increment epoch w/o writing to the trace as well. - TraceAddEvent(thr, thr->fast_state.epoch(), EventTypeMop, 0); + TraceAddEvent(thr, thr->fast_state, EventTypeMop, 0); thr->clock.set(thr->tid, thr->fast_state.epoch()); thr->fast_synch_epoch = thr->fast_state.epoch(); thr->clock.release(&tctx->sync); @@ -230,14 +261,16 @@ void ThreadFinish(ThreadState *thr) { // Save from info about the thread. tctx->dead_info = new(internal_alloc(MBlockDeadInfo, sizeof(ThreadDeadInfo))) ThreadDeadInfo(); - internal_memcpy(&tctx->dead_info->trace.events[0], - &thr->trace.events[0], sizeof(thr->trace.events)); - for (int i = 0; i < kTraceParts; i++) { + for (uptr i = 0; i < TraceParts(); i++) { + tctx->dead_info->trace.headers[i].epoch0 = thr->trace.headers[i].epoch0; tctx->dead_info->trace.headers[i].stack0.CopyFrom( thr->trace.headers[i].stack0); } tctx->epoch1 = thr->fast_state.epoch(); +#ifndef TSAN_GO + AlloctorThreadFinish(thr); +#endif thr->~ThreadState(); StatAggregate(ctx->stat, thr->stat); tctx->thr = 0; @@ -270,9 +303,10 @@ void ThreadJoin(ThreadState *thr, uptr pc, int tid) { Lock l(&ctx->thread_mtx); ThreadContext *tctx = ctx->threads[tid]; if (tctx->status == ThreadStatusInvalid) { - TsanPrintf("ThreadSanitizer: join of non-existent thread\n"); + Printf("ThreadSanitizer: join of non-existent thread\n"); return; } + // FIXME(dvyukov): print message and continue (it's user error). CHECK_EQ(tctx->detached, false); CHECK_EQ(tctx->status, ThreadStatusFinished); thr->clock.acquire(&tctx->sync); @@ -288,7 +322,7 @@ void ThreadDetach(ThreadState *thr, uptr pc, int tid) { Lock l(&ctx->thread_mtx); ThreadContext *tctx = ctx->threads[tid]; if (tctx->status == ThreadStatusInvalid) { - TsanPrintf("ThreadSanitizer: detach of non-existent thread\n"); + Printf("ThreadSanitizer: detach of non-existent thread\n"); return; } if (tctx->status == ThreadStatusFinished) { @@ -298,8 +332,18 @@ void ThreadDetach(ThreadState *thr, uptr pc, int tid) { } } -void ThreadFinalizerGoroutine(ThreadState *thr) { - thr->clock.Disable(thr->tid); +void ThreadSetName(ThreadState *thr, const char *name) { + Context *ctx = CTX(); + Lock l(&ctx->thread_mtx); + ThreadContext *tctx = ctx->threads[thr->tid]; + CHECK_NE(tctx, 0); + CHECK_EQ(tctx->status, ThreadStatusRunning); + if (tctx->name) { + internal_free(tctx->name); + tctx->name = 0; + } + if (name) + tctx->name = internal_strdup(name); } void MemoryAccessRange(ThreadState *thr, uptr pc, uptr addr, @@ -314,19 +358,19 @@ void MemoryAccessRange(ThreadState *thr, uptr pc, uptr addr, #if TSAN_DEBUG if (!IsAppMem(addr)) { - TsanPrintf("Access to non app mem %zx\n", addr); + Printf("Access to non app mem %zx\n", addr); DCHECK(IsAppMem(addr)); } if (!IsAppMem(addr + size - 1)) { - TsanPrintf("Access to non app mem %zx\n", addr + size - 1); + Printf("Access to non app mem %zx\n", addr + size - 1); DCHECK(IsAppMem(addr + size - 1)); } if (!IsShadowMem((uptr)shadow_mem)) { - TsanPrintf("Bad shadow addr %p (%zx)\n", shadow_mem, addr); + Printf("Bad shadow addr %p (%zx)\n", shadow_mem, addr); DCHECK(IsShadowMem((uptr)shadow_mem)); } if (!IsShadowMem((uptr)(shadow_mem + size * kShadowCnt / 8 - 1))) { - TsanPrintf("Bad shadow addr %p (%zx)\n", + Printf("Bad shadow addr %p (%zx)\n", shadow_mem + size * kShadowCnt / 8 - 1, addr + size - 1); DCHECK(IsShadowMem((uptr)(shadow_mem + size * kShadowCnt / 8 - 1))); } @@ -340,7 +384,7 @@ void MemoryAccessRange(ThreadState *thr, uptr pc, uptr addr, fast_state.IncrementEpoch(); thr->fast_state = fast_state; - TraceAddEvent(thr, fast_state.epoch(), EventTypeMop, pc); + TraceAddEvent(thr, fast_state, EventTypeMop, pc); bool unaligned = (addr % kShadowCell) != 0; @@ -350,7 +394,7 @@ void MemoryAccessRange(ThreadState *thr, uptr pc, uptr addr, Shadow cur(fast_state); cur.SetWrite(is_write); cur.SetAddr0AndSizeLog(addr & (kShadowCell - 1), kAccessSizeLog); - MemoryAccessImpl(thr, addr, kAccessSizeLog, is_write, fast_state, + MemoryAccessImpl(thr, addr, kAccessSizeLog, is_write, shadow_mem, cur); } if (unaligned) @@ -361,7 +405,7 @@ void MemoryAccessRange(ThreadState *thr, uptr pc, uptr addr, Shadow cur(fast_state); cur.SetWrite(is_write); cur.SetAddr0AndSizeLog(0, kAccessSizeLog); - MemoryAccessImpl(thr, addr, kAccessSizeLog, is_write, fast_state, + MemoryAccessImpl(thr, addr, kAccessSizeLog, is_write, shadow_mem, cur); shadow_mem += kShadowCnt; } @@ -371,7 +415,7 @@ void MemoryAccessRange(ThreadState *thr, uptr pc, uptr addr, Shadow cur(fast_state); cur.SetWrite(is_write); cur.SetAddr0AndSizeLog(addr & (kShadowCell - 1), kAccessSizeLog); - MemoryAccessImpl(thr, addr, kAccessSizeLog, is_write, fast_state, + MemoryAccessImpl(thr, addr, kAccessSizeLog, is_write, shadow_mem, cur); } } diff --git a/lib/tsan/rtl/tsan_stat.cc b/lib/tsan/rtl/tsan_stat.cc index a7c33a5de760..82f1d6b5620f 100644 --- a/lib/tsan/rtl/tsan_stat.cc +++ b/lib/tsan/rtl/tsan_stat.cc @@ -77,6 +77,11 @@ void StatOutput(u64 *stat) { name[StatAtomicStore] = " store "; name[StatAtomicExchange] = " exchange "; name[StatAtomicFetchAdd] = " fetch_add "; + name[StatAtomicFetchSub] = " fetch_sub "; + name[StatAtomicFetchAnd] = " fetch_and "; + name[StatAtomicFetchOr] = " fetch_or "; + name[StatAtomicFetchXor] = " fetch_xor "; + name[StatAtomicFetchNand] = " fetch_nand "; name[StatAtomicCAS] = " compare_exchange "; name[StatAtomicFence] = " fence "; name[StatAtomicRelaxed] = " Including relaxed "; @@ -89,11 +94,13 @@ void StatOutput(u64 *stat) { name[StatAtomic2] = " size 2 "; name[StatAtomic4] = " size 4 "; name[StatAtomic8] = " size 8 "; + name[StatAtomic16] = " size 16 "; name[StatInterceptor] = "Interceptors "; name[StatInt_longjmp] = " longjmp "; name[StatInt_siglongjmp] = " siglongjmp "; name[StatInt_malloc] = " malloc "; + name[StatInt___libc_memalign] = " __libc_memalign "; name[StatInt_calloc] = " calloc "; name[StatInt_realloc] = " realloc "; name[StatInt_free] = " free "; @@ -131,6 +138,7 @@ void StatOutput(u64 *stat) { name[StatInt_atexit] = " atexit "; name[StatInt___cxa_guard_acquire] = " __cxa_guard_acquire "; name[StatInt___cxa_guard_release] = " __cxa_guard_release "; + name[StatInt___cxa_guard_abort] = " __cxa_guard_abort "; name[StatInt_pthread_create] = " pthread_create "; name[StatInt_pthread_join] = " pthread_join "; name[StatInt_pthread_detach] = " pthread_detach "; @@ -173,7 +181,30 @@ void StatOutput(u64 *stat) { name[StatInt_sem_timedwait] = " sem_timedwait "; name[StatInt_sem_post] = " sem_post "; name[StatInt_sem_getvalue] = " sem_getvalue "; + name[StatInt_open] = " open "; + name[StatInt_open64] = " open64 "; + name[StatInt_creat] = " creat "; + name[StatInt_creat64] = " creat64 "; + name[StatInt_dup] = " dup "; + name[StatInt_dup2] = " dup2 "; + name[StatInt_dup3] = " dup3 "; + name[StatInt_eventfd] = " eventfd "; + name[StatInt_signalfd] = " signalfd "; + name[StatInt_inotify_init] = " inotify_init "; + name[StatInt_inotify_init1] = " inotify_init1 "; + name[StatInt_socket] = " socket "; + name[StatInt_socketpair] = " socketpair "; + name[StatInt_connect] = " connect "; + name[StatInt_accept] = " accept "; + name[StatInt_accept4] = " accept4 "; + name[StatInt_epoll_create] = " epoll_create "; + name[StatInt_epoll_create1] = " epoll_create1 "; + name[StatInt_close] = " close "; + name[StatInt___close] = " __close "; + name[StatInt_pipe] = " pipe "; + name[StatInt_pipe2] = " pipe2 "; name[StatInt_read] = " read "; + name[StatInt_prctl] = " prctl "; name[StatInt_pread] = " pread "; name[StatInt_pread64] = " pread64 "; name[StatInt_readv] = " readv "; @@ -189,6 +220,8 @@ void StatOutput(u64 *stat) { name[StatInt_recvmsg] = " recvmsg "; name[StatInt_unlink] = " unlink "; name[StatInt_fopen] = " fopen "; + name[StatInt_freopen] = " freopen "; + name[StatInt_fclose] = " fclose "; name[StatInt_fread] = " fread "; name[StatInt_fwrite] = " fwrite "; name[StatInt_puts] = " puts "; @@ -196,7 +229,19 @@ void StatOutput(u64 *stat) { name[StatInt_opendir] = " opendir "; name[StatInt_epoll_ctl] = " epoll_ctl "; name[StatInt_epoll_wait] = " epoll_wait "; + name[StatInt_poll] = " poll "; name[StatInt_sigaction] = " sigaction "; + name[StatInt_sleep] = " sleep "; + name[StatInt_usleep] = " usleep "; + name[StatInt_nanosleep] = " nanosleep "; + name[StatInt_gettimeofday] = " gettimeofday "; + name[StatInt_fork] = " fork "; + name[StatInt_vscanf] = " vscanf "; + name[StatInt_vsscanf] = " vsscanf "; + name[StatInt_vfscanf] = " vfscanf "; + name[StatInt_scanf] = " scanf "; + name[StatInt_sscanf] = " sscanf "; + name[StatInt_fscanf] = " fscanf "; name[StatAnnotation] = "Dynamic annotations "; name[StatAnnotateHappensBefore] = " HappensBefore "; @@ -240,10 +285,12 @@ void StatOutput(u64 *stat) { name[StatMtxSlab] = " Slab "; name[StatMtxAtExit] = " Atexit "; name[StatMtxAnnotations] = " Annotations "; + name[StatMtxMBlock] = " MBlock "; + name[StatMtxJavaMBlock] = " JavaMBlock "; - TsanPrintf("Statistics:\n"); + Printf("Statistics:\n"); for (int i = 0; i < StatCnt; i++) - TsanPrintf("%s: %zu\n", name[i], (uptr)stat[i]); + Printf("%s: %zu\n", name[i], (uptr)stat[i]); } } // namespace __tsan diff --git a/lib/tsan/rtl/tsan_stat.h b/lib/tsan/rtl/tsan_stat.h index 71b1b13c0876..58c5f23af40b 100644 --- a/lib/tsan/rtl/tsan_stat.h +++ b/lib/tsan/rtl/tsan_stat.h @@ -73,9 +73,11 @@ enum StatType { StatAtomicStore, StatAtomicExchange, StatAtomicFetchAdd, + StatAtomicFetchSub, StatAtomicFetchAnd, StatAtomicFetchOr, StatAtomicFetchXor, + StatAtomicFetchNand, StatAtomicCAS, StatAtomicFence, StatAtomicRelaxed, @@ -88,12 +90,14 @@ enum StatType { StatAtomic2, StatAtomic4, StatAtomic8, + StatAtomic16, // Interceptors. StatInterceptor, StatInt_longjmp, StatInt_siglongjmp, StatInt_malloc, + StatInt___libc_memalign, StatInt_calloc, StatInt_realloc, StatInt_free, @@ -131,6 +135,7 @@ enum StatType { StatInt_atexit, StatInt___cxa_guard_acquire, StatInt___cxa_guard_release, + StatInt___cxa_guard_abort, StatInt_pthread_create, StatInt_pthread_join, StatInt_pthread_detach, @@ -171,7 +176,30 @@ enum StatType { StatInt_sem_timedwait, StatInt_sem_post, StatInt_sem_getvalue, + StatInt_open, + StatInt_open64, + StatInt_creat, + StatInt_creat64, + StatInt_dup, + StatInt_dup2, + StatInt_dup3, + StatInt_eventfd, + StatInt_signalfd, + StatInt_inotify_init, + StatInt_inotify_init1, + StatInt_socket, + StatInt_socketpair, + StatInt_connect, + StatInt_accept, + StatInt_accept4, + StatInt_epoll_create, + StatInt_epoll_create1, + StatInt_close, + StatInt___close, + StatInt_pipe, + StatInt_pipe2, StatInt_read, + StatInt_prctl, StatInt_pread, StatInt_pread64, StatInt_readv, @@ -187,6 +215,8 @@ enum StatType { StatInt_recvmsg, StatInt_unlink, StatInt_fopen, + StatInt_freopen, + StatInt_fclose, StatInt_fread, StatInt_fwrite, StatInt_puts, @@ -194,11 +224,23 @@ enum StatType { StatInt_opendir, StatInt_epoll_ctl, StatInt_epoll_wait, + StatInt_poll, StatInt_sigaction, StatInt_signal, StatInt_raise, StatInt_kill, StatInt_pthread_kill, + StatInt_sleep, + StatInt_usleep, + StatInt_nanosleep, + StatInt_gettimeofday, + StatInt_fork, + StatInt_vscanf, + StatInt_vsscanf, + StatInt_vfscanf, + StatInt_scanf, + StatInt_sscanf, + StatInt_fscanf, // Dynamic annotations. StatAnnotation, @@ -209,6 +251,7 @@ enum StatType { StatAnnotateMutexIsNotPHB, StatAnnotateCondVarWait, StatAnnotateRWLockCreate, + StatAnnotateRWLockCreateStatic, StatAnnotateRWLockDestroy, StatAnnotateRWLockAcquired, StatAnnotateRWLockReleased, @@ -244,9 +287,11 @@ enum StatType { StatMtxSlab, StatMtxAnnotations, StatMtxAtExit, + StatMtxMBlock, + StatMtxJavaMBlock, // This must be the last. - StatCnt, + StatCnt }; } // namespace __tsan diff --git a/lib/tsan/rtl/tsan_suppressions.cc b/lib/tsan/rtl/tsan_suppressions.cc index 7549a4f8ba83..5316f6db6a0a 100644 --- a/lib/tsan/rtl/tsan_suppressions.cc +++ b/lib/tsan/rtl/tsan_suppressions.cc @@ -26,27 +26,27 @@ static Suppression *g_suppressions; static char *ReadFile(const char *filename) { if (filename == 0 || filename[0] == 0) return 0; - InternalScopedBuf<char> tmp(4*1024); - if (filename[0] == '/') - internal_snprintf(tmp, tmp.Size(), "%s", filename); + InternalScopedBuffer<char> tmp(4*1024); + if (filename[0] == '/' || GetPwd() == 0) + internal_snprintf(tmp.data(), tmp.size(), "%s", filename); else - internal_snprintf(tmp, tmp.Size(), "%s/%s", GetPwd(), filename); - fd_t fd = internal_open(tmp, false); + internal_snprintf(tmp.data(), tmp.size(), "%s/%s", GetPwd(), filename); + fd_t fd = internal_open(tmp.data(), false); if (fd == kInvalidFd) { - TsanPrintf("ThreadSanitizer: failed to open suppressions file '%s'\n", - tmp.Ptr()); + Printf("ThreadSanitizer: failed to open suppressions file '%s'\n", + tmp.data()); Die(); } const uptr fsize = internal_filesize(fd); if (fsize == (uptr)-1) { - TsanPrintf("ThreadSanitizer: failed to stat suppressions file '%s'\n", - tmp.Ptr()); + Printf("ThreadSanitizer: failed to stat suppressions file '%s'\n", + tmp.data()); Die(); } char *buf = (char*)internal_alloc(MBlockSuppression, fsize + 1); if (fsize != internal_read(fd, buf, fsize)) { - TsanPrintf("ThreadSanitizer: failed to read suppressions file '%s'\n", - tmp.Ptr()); + Printf("ThreadSanitizer: failed to read suppressions file '%s'\n", + tmp.data()); Die(); } internal_close(fd); @@ -110,7 +110,7 @@ Suppression *SuppressionParse(const char* supp) { stype = SuppressionSignal; line += sizeof("signal:") - 1; } else { - TsanPrintf("ThreadSanitizer: failed to parse suppressions file\n"); + Printf("ThreadSanitizer: failed to parse suppressions file\n"); Die(); } Suppression *s = (Suppression*)internal_alloc(MBlockSuppression, @@ -134,9 +134,9 @@ void InitializeSuppressions() { g_suppressions = SuppressionParse(supp); } -bool IsSuppressed(ReportType typ, const ReportStack *stack) { +uptr IsSuppressed(ReportType typ, const ReportStack *stack) { if (g_suppressions == 0 || stack == 0) - return false; + return 0; SuppressionType stype; if (typ == ReportTypeRace) stype = SuppressionRace; @@ -147,17 +147,17 @@ bool IsSuppressed(ReportType typ, const ReportStack *stack) { else if (typ == ReportTypeSignalUnsafe) stype = SuppressionSignal; else - return false; + return 0; for (const ReportStack *frame = stack; frame; frame = frame->next) { for (Suppression *supp = g_suppressions; supp; supp = supp->next) { if (stype == supp->type && (SuppressionMatch(supp->templ, frame->func) || SuppressionMatch(supp->templ, frame->file))) { DPrintf("ThreadSanitizer: matched suppression '%s'\n", supp->templ); - return true; + return frame->pc; } } } - return false; + return 0; } } // namespace __tsan diff --git a/lib/tsan/rtl/tsan_suppressions.h b/lib/tsan/rtl/tsan_suppressions.h index 29311c123d6d..61a4cca9d17a 100644 --- a/lib/tsan/rtl/tsan_suppressions.h +++ b/lib/tsan/rtl/tsan_suppressions.h @@ -19,14 +19,14 @@ namespace __tsan { void InitializeSuppressions(); void FinalizeSuppressions(); -bool IsSuppressed(ReportType typ, const ReportStack *stack); +uptr IsSuppressed(ReportType typ, const ReportStack *stack); // Exposed for testing. enum SuppressionType { SuppressionRace, SuppressionMutex, SuppressionThread, - SuppressionSignal, + SuppressionSignal }; struct Suppression { diff --git a/lib/tsan/rtl/tsan_symbolize.cc b/lib/tsan/rtl/tsan_symbolize.cc index f757d070e169..29dfe237ffd9 100644 --- a/lib/tsan/rtl/tsan_symbolize.cc +++ b/lib/tsan/rtl/tsan_symbolize.cc @@ -29,14 +29,24 @@ ReportStack *NewReportStackEntry(uptr addr) { return ent; } +// Strip module path to make output shorter. +static char *StripModuleName(const char *module) { + if (module == 0) + return 0; + const char *short_module_name = internal_strrchr(module, '/'); + if (short_module_name) + short_module_name += 1; + else + short_module_name = module; + return internal_strdup(short_module_name); +} + static ReportStack *NewReportStackEntry(const AddressInfo &info) { ReportStack *ent = NewReportStackEntry(info.address); - if (info.module) - ent->module = internal_strdup(info.module); + ent->module = StripModuleName(info.module); ent->offset = info.module_offset; - if (info.function) { + if (info.function) ent->func = internal_strdup(info.function); - } if (info.file) ent->file = internal_strdup(info.file); ent->line = info.line; @@ -45,12 +55,12 @@ static ReportStack *NewReportStackEntry(const AddressInfo &info) { } ReportStack *SymbolizeCode(uptr addr) { - if (flags()->use_internal_symbolizer) { + if (flags()->external_symbolizer_path[0]) { static const uptr kMaxAddrFrames = 16; - InternalScopedBuf<AddressInfo> addr_frames(kMaxAddrFrames); + InternalScopedBuffer<AddressInfo> addr_frames(kMaxAddrFrames); for (uptr i = 0; i < kMaxAddrFrames; i++) new(&addr_frames[i]) AddressInfo(); - uptr addr_frames_num = __sanitizer::SymbolizeCode(addr, addr_frames, + uptr addr_frames_num = __sanitizer::SymbolizeCode(addr, addr_frames.data(), kMaxAddrFrames); if (addr_frames_num == 0) return NewReportStackEntry(addr); @@ -71,8 +81,23 @@ ReportStack *SymbolizeCode(uptr addr) { return SymbolizeCodeAddr2Line(addr); } -ReportStack *SymbolizeData(uptr addr) { - return SymbolizeDataAddr2Line(addr); +ReportLocation *SymbolizeData(uptr addr) { + if (flags()->external_symbolizer_path[0] == 0) + return 0; + DataInfo info; + if (!__sanitizer::SymbolizeData(addr, &info)) + return 0; + ReportLocation *ent = (ReportLocation*)internal_alloc(MBlockReportStack, + sizeof(ReportLocation)); + internal_memset(ent, 0, sizeof(*ent)); + ent->type = ReportLocationGlobal; + ent->module = StripModuleName(info.module); + ent->offset = info.module_offset; + if (info.name) + ent->name = internal_strdup(info.name); + ent->addr = info.start; + ent->size = info.size; + return ent; } } // namespace __tsan diff --git a/lib/tsan/rtl/tsan_symbolize.h b/lib/tsan/rtl/tsan_symbolize.h index 115339be38a9..29193043cd70 100644 --- a/lib/tsan/rtl/tsan_symbolize.h +++ b/lib/tsan/rtl/tsan_symbolize.h @@ -19,10 +19,9 @@ namespace __tsan { ReportStack *SymbolizeCode(uptr addr); -ReportStack *SymbolizeData(uptr addr); +ReportLocation *SymbolizeData(uptr addr); ReportStack *SymbolizeCodeAddr2Line(uptr addr); -ReportStack *SymbolizeDataAddr2Line(uptr addr); ReportStack *NewReportStackEntry(uptr addr); diff --git a/lib/tsan/rtl/tsan_symbolize_addr2line_linux.cc b/lib/tsan/rtl/tsan_symbolize_addr2line_linux.cc index 5eed977ec278..76926e2b5aaf 100644 --- a/lib/tsan/rtl/tsan_symbolize_addr2line_linux.cc +++ b/lib/tsan/rtl/tsan_symbolize_addr2line_linux.cc @@ -50,17 +50,17 @@ struct DlIteratePhdrCtx { static void NOINLINE InitModule(ModuleDesc *m) { int outfd[2] = {}; if (pipe(&outfd[0])) { - TsanPrintf("ThreadSanitizer: outfd pipe() failed (%d)\n", errno); + Printf("ThreadSanitizer: outfd pipe() failed (%d)\n", errno); Die(); } int infd[2] = {}; if (pipe(&infd[0])) { - TsanPrintf("ThreadSanitizer: infd pipe() failed (%d)\n", errno); + Printf("ThreadSanitizer: infd pipe() failed (%d)\n", errno); Die(); } int pid = fork(); if (pid == 0) { - flags()->log_fileno = STDERR_FILENO; + __sanitizer_set_report_fd(STDERR_FILENO); internal_close(STDOUT_FILENO); internal_close(STDIN_FILENO); internal_dup2(outfd[0], STDIN_FILENO); @@ -74,7 +74,7 @@ static void NOINLINE InitModule(ModuleDesc *m) { execl("/usr/bin/addr2line", "/usr/bin/addr2line", "-Cfe", m->fullname, 0); _exit(0); } else if (pid < 0) { - TsanPrintf("ThreadSanitizer: failed to fork symbolizer\n"); + Printf("ThreadSanitizer: failed to fork symbolizer\n"); Die(); } internal_close(outfd[0]); @@ -85,10 +85,10 @@ static void NOINLINE InitModule(ModuleDesc *m) { static int dl_iterate_phdr_cb(dl_phdr_info *info, size_t size, void *arg) { DlIteratePhdrCtx *ctx = (DlIteratePhdrCtx*)arg; - InternalScopedBuf<char> tmp(128); + InternalScopedBuffer<char> tmp(128); if (ctx->is_first) { - internal_snprintf(tmp.Ptr(), tmp.Size(), "/proc/%d/exe", GetPid()); - info->dlpi_name = tmp.Ptr(); + internal_snprintf(tmp.data(), tmp.size(), "/proc/%d/exe", GetPid()); + info->dlpi_name = tmp.data(); } ctx->is_first = false; if (info->dlpi_name == 0 || info->dlpi_name[0] == 0) @@ -104,11 +104,11 @@ static int dl_iterate_phdr_cb(dl_phdr_info *info, size_t size, void *arg) { m->base = (uptr)info->dlpi_addr; m->inp_fd = -1; m->out_fd = -1; - DPrintf("Module %s %zx\n", m->name, m->base); + DPrintf2("Module %s %zx\n", m->name, m->base); for (int i = 0; i < info->dlpi_phnum; i++) { const Elf64_Phdr *s = &info->dlpi_phdr[i]; - DPrintf(" Section p_type=%zx p_offset=%zx p_vaddr=%zx p_paddr=%zx" - " p_filesz=%zx p_memsz=%zx p_flags=%zx p_align=%zx\n", + DPrintf2(" Section p_type=%zx p_offset=%zx p_vaddr=%zx p_paddr=%zx" + " p_filesz=%zx p_memsz=%zx p_flags=%zx p_align=%zx\n", (uptr)s->p_type, (uptr)s->p_offset, (uptr)s->p_vaddr, (uptr)s->p_paddr, (uptr)s->p_filesz, (uptr)s->p_memsz, (uptr)s->p_flags, (uptr)s->p_align); @@ -121,7 +121,7 @@ static int dl_iterate_phdr_cb(dl_phdr_info *info, size_t size, void *arg) { sec->end = sec->base + s->p_memsz; sec->next = ctx->sections; ctx->sections = sec; - DPrintf(" Section %zx-%zx\n", sec->base, sec->end); + DPrintf2(" Section %zx-%zx\n", sec->base, sec->end); } return 0; } @@ -155,26 +155,26 @@ ReportStack *SymbolizeCodeAddr2Line(uptr addr) { char addrstr[32]; internal_snprintf(addrstr, sizeof(addrstr), "%p\n", (void*)offset); if (0 >= internal_write(m->out_fd, addrstr, internal_strlen(addrstr))) { - TsanPrintf("ThreadSanitizer: can't write from symbolizer (%d, %d)\n", + Printf("ThreadSanitizer: can't write from symbolizer (%d, %d)\n", m->out_fd, errno); Die(); } - InternalScopedBuf<char> func(1024); - ssize_t len = internal_read(m->inp_fd, func, func.Size() - 1); + InternalScopedBuffer<char> func(1024); + ssize_t len = internal_read(m->inp_fd, func.data(), func.size() - 1); if (len <= 0) { - TsanPrintf("ThreadSanitizer: can't read from symbolizer (%d, %d)\n", + Printf("ThreadSanitizer: can't read from symbolizer (%d, %d)\n", m->inp_fd, errno); Die(); } - func.Ptr()[len] = 0; + func.data()[len] = 0; ReportStack *res = NewReportStackEntry(addr); res->module = internal_strdup(m->name); res->offset = offset; - char *pos = (char*)internal_strchr(func, '\n'); + char *pos = (char*)internal_strchr(func.data(), '\n'); if (pos && func[0] != '?') { - res->func = (char*)internal_alloc(MBlockReportStack, pos - func + 1); - internal_memcpy(res->func, func, pos - func); - res->func[pos - func] = 0; + res->func = (char*)internal_alloc(MBlockReportStack, pos - func.data() + 1); + internal_memcpy(res->func, func.data(), pos - func.data()); + res->func[pos - func.data()] = 0; char *pos2 = (char*)internal_strchr(pos, ':'); if (pos2) { res->file = (char*)internal_alloc(MBlockReportStack, pos2 - pos - 1 + 1); diff --git a/lib/tsan/rtl/tsan_sync.cc b/lib/tsan/rtl/tsan_sync.cc index abb5a2ad298f..b25346ef344f 100644 --- a/lib/tsan/rtl/tsan_sync.cc +++ b/lib/tsan/rtl/tsan_sync.cc @@ -17,14 +17,17 @@ namespace __tsan { -SyncVar::SyncVar(uptr addr) +SyncVar::SyncVar(uptr addr, u64 uid) : mtx(MutexTypeSyncVar, StatMtxSyncVar) , addr(addr) + , uid(uid) , owner_tid(kInvalidTid) + , last_lock() , recursion() , is_rw() , is_recursive() - , is_broken() { + , is_broken() + , is_linker_init() { } SyncTab::Part::Part() @@ -45,8 +48,61 @@ SyncTab::~SyncTab() { } } +SyncVar* SyncTab::GetOrCreateAndLock(ThreadState *thr, uptr pc, + uptr addr, bool write_lock) { + return GetAndLock(thr, pc, addr, write_lock, true); +} + +SyncVar* SyncTab::GetIfExistsAndLock(uptr addr, bool write_lock) { + return GetAndLock(0, 0, addr, write_lock, false); +} + +SyncVar* SyncTab::Create(ThreadState *thr, uptr pc, uptr addr) { + StatInc(thr, StatSyncCreated); + void *mem = internal_alloc(MBlockSync, sizeof(SyncVar)); + const u64 uid = atomic_fetch_add(&uid_gen_, 1, memory_order_relaxed); + SyncVar *res = new(mem) SyncVar(addr, uid); +#ifndef TSAN_GO + res->creation_stack.ObtainCurrent(thr, pc); +#endif + return res; +} + SyncVar* SyncTab::GetAndLock(ThreadState *thr, uptr pc, - uptr addr, bool write_lock) { + uptr addr, bool write_lock, bool create) { +#ifndef TSAN_GO + { // NOLINT + SyncVar *res = GetJavaSync(thr, pc, addr, write_lock, create); + if (res) + return res; + } + + // Here we ask only PrimaryAllocator, because + // SecondaryAllocator::PointerIsMine() is slow and we have fallback on + // the hashmap anyway. + if (PrimaryAllocator::PointerIsMine((void*)addr)) { + MBlock *b = user_mblock(thr, (void*)addr); + Lock l(&b->mtx); + SyncVar *res = 0; + for (res = b->head; res; res = res->next) { + if (res->addr == addr) + break; + } + if (res == 0) { + if (!create) + return 0; + res = Create(thr, pc, addr); + res->next = b->head; + b->head = res; + } + if (write_lock) + res->mtx.Lock(); + else + res->mtx.ReadLock(); + return res; + } +#endif + Part *p = &tab_[PartIdx(addr)]; { ReadLock l(&p->mtx); @@ -60,6 +116,8 @@ SyncVar* SyncTab::GetAndLock(ThreadState *thr, uptr pc, } } } + if (!create) + return 0; { Lock l(&p->mtx); SyncVar *res = p->val; @@ -68,12 +126,7 @@ SyncVar* SyncTab::GetAndLock(ThreadState *thr, uptr pc, break; } if (res == 0) { - StatInc(thr, StatSyncCreated); - void *mem = internal_alloc(MBlockSync, sizeof(SyncVar)); - res = new(mem) SyncVar(addr); -#ifndef TSAN_GO - res->creation_stack.ObtainCurrent(thr, pc); -#endif + res = Create(thr, pc, addr); res->next = p->val; p->val = res; } @@ -86,6 +139,39 @@ SyncVar* SyncTab::GetAndLock(ThreadState *thr, uptr pc, } SyncVar* SyncTab::GetAndRemove(ThreadState *thr, uptr pc, uptr addr) { +#ifndef TSAN_GO + { // NOLINT + SyncVar *res = GetAndRemoveJavaSync(thr, pc, addr); + if (res) + return res; + } + if (PrimaryAllocator::PointerIsMine((void*)addr)) { + MBlock *b = user_mblock(thr, (void*)addr); + SyncVar *res = 0; + { + Lock l(&b->mtx); + SyncVar **prev = &b->head; + res = *prev; + while (res) { + if (res->addr == addr) { + if (res->is_linker_init) + return 0; + *prev = res->next; + break; + } + prev = &res->next; + res = *prev; + } + } + if (res) { + StatInc(thr, StatSyncDestroyed); + res->mtx.Lock(); + res->mtx.Unlock(); + } + return res; + } +#endif + Part *p = &tab_[PartIdx(addr)]; SyncVar *res = 0; { @@ -94,6 +180,8 @@ SyncVar* SyncTab::GetAndRemove(ThreadState *thr, uptr pc, uptr addr) { res = *prev; while (res) { if (res->addr == addr) { + if (res->is_linker_init) + return 0; *prev = res->next; break; } @@ -179,15 +267,19 @@ void StackTrace::ObtainCurrent(ThreadState *thr, uptr toppc) { n_ = thr->shadow_stack_pos - thr->shadow_stack; if (n_ + !!toppc == 0) return; + uptr start = 0; if (c_) { CHECK_NE(s_, 0); - CHECK_LE(n_ + !!toppc, c_); + if (n_ + !!toppc > c_) { + start = n_ - c_ + !!toppc; + n_ = c_ - !!toppc; + } } else { s_ = (uptr*)internal_alloc(MBlockStackTrace, (n_ + !!toppc) * sizeof(s_[0])); } for (uptr i = 0; i < n_; i++) - s_[i] = thr->shadow_stack[i]; + s_[i] = thr->shadow_stack[start + i]; if (toppc) { s_[n_] = toppc; n_++; diff --git a/lib/tsan/rtl/tsan_sync.h b/lib/tsan/rtl/tsan_sync.h index 34d3e0b2132f..77749e22ffc2 100644 --- a/lib/tsan/rtl/tsan_sync.h +++ b/lib/tsan/rtl/tsan_sync.h @@ -50,23 +50,38 @@ class StackTrace { }; struct SyncVar { - explicit SyncVar(uptr addr); + explicit SyncVar(uptr addr, u64 uid); static const int kInvalidTid = -1; Mutex mtx; - const uptr addr; + uptr addr; + const u64 uid; // Globally unique id. SyncClock clock; SyncClock read_clock; // Used for rw mutexes only. StackTrace creation_stack; int owner_tid; // Set only by exclusive owners. + u64 last_lock; int recursion; bool is_rw; bool is_recursive; bool is_broken; + bool is_linker_init; SyncVar *next; // In SyncTab hashtable. uptr GetMemoryConsumption(); + u64 GetId() const { + // 47 lsb is addr, then 14 bits is low part of uid, then 3 zero bits. + return GetLsb((u64)addr | (uid << 47), 61); + } + bool CheckId(u64 uid) const { + CHECK_EQ(uid, GetLsb(uid, 14)); + return GetLsb(this->uid, 14) == uid; + } + static uptr SplitId(u64 id, u64 *uid) { + *uid = id >> 47; + return (uptr)GetLsb(id, 47); + } }; class SyncTab { @@ -74,13 +89,15 @@ class SyncTab { SyncTab(); ~SyncTab(); - // If the SyncVar does not exist yet, it is created. - SyncVar* GetAndLock(ThreadState *thr, uptr pc, - uptr addr, bool write_lock); + SyncVar* GetOrCreateAndLock(ThreadState *thr, uptr pc, + uptr addr, bool write_lock); + SyncVar* GetIfExistsAndLock(uptr addr, bool write_lock); // If the SyncVar does not exist, returns 0. SyncVar* GetAndRemove(ThreadState *thr, uptr pc, uptr addr); + SyncVar* Create(ThreadState *thr, uptr pc, uptr addr); + uptr GetMemoryConsumption(uptr *nsync); private: @@ -94,9 +111,13 @@ class SyncTab { // FIXME: Implement something more sane. static const int kPartCount = 1009; Part tab_[kPartCount]; + atomic_uint64_t uid_gen_; int PartIdx(uptr addr); + SyncVar* GetAndLock(ThreadState *thr, uptr pc, + uptr addr, bool write_lock, bool create); + SyncTab(const SyncTab&); // Not implemented. void operator = (const SyncTab&); // Not implemented. }; diff --git a/lib/tsan/rtl/tsan_trace.h b/lib/tsan/rtl/tsan_trace.h index bf15bf5cc239..7df716046567 100644 --- a/lib/tsan/rtl/tsan_trace.h +++ b/lib/tsan/rtl/tsan_trace.h @@ -16,12 +16,14 @@ #include "tsan_defs.h" #include "tsan_mutex.h" #include "tsan_sync.h" +#include "tsan_mutexset.h" namespace __tsan { -const int kTraceParts = 8; -const int kTraceSize = 128*1024; -const int kTracePartSize = kTraceSize / kTraceParts; +const int kTracePartSizeBits = 14; +const int kTracePartSize = 1 << kTracePartSizeBits; +const int kTraceParts = 4 * 1024 * 1024 / kTracePartSize; +const int kTraceSize = kTracePartSize * kTraceParts; // Must fit into 3 bits. enum EventType { @@ -31,7 +33,7 @@ enum EventType { EventTypeLock, EventTypeUnlock, EventTypeRLock, - EventTypeRUnlock, + EventTypeRUnlock }; // Represents a thread event (from most significant bit): @@ -42,13 +44,14 @@ typedef u64 Event; struct TraceHeader { StackTrace stack0; // Start stack for the trace. u64 epoch0; // Start epoch for the trace. + MutexSet mset0; #ifndef TSAN_GO - uptr stack0buf[kShadowStackSize]; + uptr stack0buf[kTraceStackSize]; #endif TraceHeader() #ifndef TSAN_GO - : stack0(stack0buf, kShadowStackSize) + : stack0(stack0buf, kTraceStackSize) #else : stack0() #endif @@ -57,7 +60,6 @@ struct TraceHeader { }; struct Trace { - Event events[kTraceSize]; TraceHeader headers[kTraceParts]; Mutex mtx; diff --git a/lib/tsan/rtl/tsan_update_shadow_word_inl.h b/lib/tsan/rtl/tsan_update_shadow_word_inl.h index c7864ce00af3..2c435556abb2 100644 --- a/lib/tsan/rtl/tsan_update_shadow_word_inl.h +++ b/lib/tsan/rtl/tsan_update_shadow_word_inl.h @@ -34,7 +34,7 @@ do { if (Shadow::TidsAreEqual(old, cur)) { StatInc(thr, StatShadowSameThread); if (OldIsInSameSynchEpoch(old, thr)) { - if (OldIsRWStronger(old, kAccessIsWrite)) { + if (OldIsRWNotWeaker(old, kAccessIsWrite)) { // found a slot that holds effectively the same info // (that is, same tid, same sync epoch and same size) StatInc(thr, StatMopSame); @@ -43,7 +43,7 @@ do { StoreIfNotYetStored(sp, &store_word); break; } - if (OldIsRWWeaker(old, kAccessIsWrite)) + if (OldIsRWWeakerOrEqual(old, kAccessIsWrite)) StoreIfNotYetStored(sp, &store_word); break; } diff --git a/lib/tsan/tests/CMakeLists.txt b/lib/tsan/tests/CMakeLists.txt new file mode 100644 index 000000000000..0fcc6b2b1c8f --- /dev/null +++ b/lib/tsan/tests/CMakeLists.txt @@ -0,0 +1,24 @@ +include_directories(../rtl) + +add_custom_target(TsanUnitTests) +set_target_properties(TsanUnitTests PROPERTIES + FOLDER "TSan unittests") +function(add_tsan_unittest testname) + # Build unit tests only on 64-bit Linux. + if(UNIX AND NOT APPLE + AND CAN_TARGET_x86_64 + AND CMAKE_SIZEOF_VOID_P EQUAL 8 + AND NOT LLVM_BUILD_32_BITS) + add_unittest(TsanUnitTests ${testname} ${ARGN}) + # Link with TSan runtime. + target_link_libraries(${testname} clang_rt.tsan-x86_64) + # Build tests with PIE and debug info. + set_property(TARGET ${testname} APPEND_STRING + PROPERTY COMPILE_FLAGS " -fPIE -g") + set_property(TARGET ${testname} APPEND_STRING + PROPERTY LINK_FLAGS " -pie") + endif() +endfunction() + +add_subdirectory(rtl) +add_subdirectory(unit) diff --git a/lib/tsan/tests/rtl/CMakeLists.txt b/lib/tsan/tests/rtl/CMakeLists.txt new file mode 100644 index 000000000000..b585660e8b4a --- /dev/null +++ b/lib/tsan/tests/rtl/CMakeLists.txt @@ -0,0 +1,15 @@ +set(TSAN_RTL_TESTS + tsan_bench.cc + tsan_mop.cc + tsan_mutex.cc + tsan_posix.cc + tsan_string.cc + tsan_test.cc + tsan_thread.cc + ) + +if(UNIX AND NOT APPLE) + list(APPEND TSAN_RTL_TESTS tsan_test_util_linux.cc) +endif() + +add_tsan_unittest(TsanRtlTest ${TSAN_RTL_TESTS}) diff --git a/lib/tsan/rtl_tests/tsan_bench.cc b/lib/tsan/tests/rtl/tsan_bench.cc index a3cf22f2c626..a3cf22f2c626 100644 --- a/lib/tsan/rtl_tests/tsan_bench.cc +++ b/lib/tsan/tests/rtl/tsan_bench.cc diff --git a/lib/tsan/rtl_tests/tsan_mop.cc b/lib/tsan/tests/rtl/tsan_mop.cc index f21742825050..f21742825050 100644 --- a/lib/tsan/rtl_tests/tsan_mop.cc +++ b/lib/tsan/tests/rtl/tsan_mop.cc diff --git a/lib/tsan/rtl_tests/tsan_mutex.cc b/lib/tsan/tests/rtl/tsan_mutex.cc index 4d9c77961818..4d9c77961818 100644 --- a/lib/tsan/rtl_tests/tsan_mutex.cc +++ b/lib/tsan/tests/rtl/tsan_mutex.cc diff --git a/lib/tsan/rtl_tests/tsan_posix.cc b/lib/tsan/tests/rtl/tsan_posix.cc index 0caedd7207e6..0caedd7207e6 100644 --- a/lib/tsan/rtl_tests/tsan_posix.cc +++ b/lib/tsan/tests/rtl/tsan_posix.cc diff --git a/lib/tsan/rtl_tests/tsan_string.cc b/lib/tsan/tests/rtl/tsan_string.cc index 75adc6c85ee9..c402f7cbd679 100644 --- a/lib/tsan/rtl_tests/tsan_string.cc +++ b/lib/tsan/tests/rtl/tsan_string.cc @@ -46,6 +46,9 @@ TEST(ThreadSanitizer, MemcpyRace1) { t2.Memcpy(data, data2, 10, true); } +// The test fails with TSAN_SHADOW_COUNT=2, +// because the old racy access is evicted. +#if defined(TSAN_SHADOW_COUNT) && TSAN_SHADOW_COUNT >= 4 TEST(ThreadSanitizer, MemcpyRace2) { char *data = new char[10]; char *data1 = new char[10]; @@ -54,6 +57,7 @@ TEST(ThreadSanitizer, MemcpyRace2) { t1.Memcpy(data+5, data1, 1); t2.Memcpy(data+3, data2, 4, true); } +#endif TEST(ThreadSanitizer, MemcpyRace3) { char *data = new char[10]; diff --git a/lib/tsan/rtl_tests/tsan_test.cc b/lib/tsan/tests/rtl/tsan_test.cc index 71641400efee..2184284d39ce 100644 --- a/lib/tsan/rtl_tests/tsan_test.cc +++ b/lib/tsan/tests/rtl/tsan_test.cc @@ -28,11 +28,13 @@ TEST(ThreadSanitizer, FuncCall) { t2.Return(); } -int main(int argc, char **argv) { +// We use this function instead of main, as ISO C++ forbids taking the address +// of main, which we need to pass inside __tsan_func_entry. +int run_tests(int argc, char **argv) { TestMutexBeforeInit(); // Mutexes must be usable before __tsan_init(); __tsan_init(); __tsan_func_entry(__builtin_return_address(0)); - __tsan_func_entry((char*)&main + 1); + __tsan_func_entry((void*)((intptr_t)&run_tests + 1)); testing::GTEST_FLAG(death_test_style) = "threadsafe"; testing::InitGoogleTest(&argc, argv); @@ -42,3 +44,7 @@ int main(int argc, char **argv) { __tsan_func_exit(); return res; } + +int main(int argc, char **argv) { + return run_tests(argc, argv); +} diff --git a/lib/tsan/rtl_tests/tsan_test_util.h b/lib/tsan/tests/rtl/tsan_test_util.h index 483a564c8475..483a564c8475 100644 --- a/lib/tsan/rtl_tests/tsan_test_util.h +++ b/lib/tsan/tests/rtl/tsan_test_util.h diff --git a/lib/tsan/rtl_tests/tsan_test_util_linux.cc b/lib/tsan/tests/rtl/tsan_test_util_linux.cc index 5bc393bf6c2e..dce8db90de70 100644 --- a/lib/tsan/rtl_tests/tsan_test_util_linux.cc +++ b/lib/tsan/tests/rtl/tsan_test_util_linux.cc @@ -397,7 +397,7 @@ void ScopedThread::VptrUpdate(const MemLoc &vptr, } void ScopedThread::Call(void(*pc)()) { - Event event(Event::CALL, (void*)pc); + Event event(Event::CALL, (void*)((uintptr_t)pc)); impl_->send(&event); } diff --git a/lib/tsan/rtl_tests/tsan_thread.cc b/lib/tsan/tests/rtl/tsan_thread.cc index 5646415a79b8..5646415a79b8 100644 --- a/lib/tsan/rtl_tests/tsan_thread.cc +++ b/lib/tsan/tests/rtl/tsan_thread.cc diff --git a/lib/tsan/tests/unit/CMakeLists.txt b/lib/tsan/tests/unit/CMakeLists.txt new file mode 100644 index 000000000000..52ebdb826939 --- /dev/null +++ b/lib/tsan/tests/unit/CMakeLists.txt @@ -0,0 +1,14 @@ +set(TSAN_UNIT_TESTS + tsan_clock_test.cc + tsan_flags_test.cc + tsan_mman_test.cc + tsan_mutex_test.cc + tsan_platform_test.cc + tsan_shadow_test.cc + tsan_stack_test.cc + tsan_suppressions_test.cc + tsan_sync_test.cc + tsan_vector_test.cc + ) + +add_tsan_unittest(TsanUnitTest ${TSAN_UNIT_TESTS}) diff --git a/lib/tsan/unit_tests/tsan_clock_test.cc b/lib/tsan/tests/unit/tsan_clock_test.cc index fe886e10bc57..fe886e10bc57 100644 --- a/lib/tsan/unit_tests/tsan_clock_test.cc +++ b/lib/tsan/tests/unit/tsan_clock_test.cc diff --git a/lib/tsan/unit_tests/tsan_flags_test.cc b/lib/tsan/tests/unit/tsan_flags_test.cc index d344cb55b1ba..ffb9c55b605f 100644 --- a/lib/tsan/unit_tests/tsan_flags_test.cc +++ b/lib/tsan/tests/unit/tsan_flags_test.cc @@ -19,14 +19,14 @@ namespace __tsan { TEST(Flags, Basic) { ScopedInRtl in_rtl; // At least should not crash. - Flags f = {}; + Flags f; InitializeFlags(&f, 0); InitializeFlags(&f, ""); } TEST(Flags, DefaultValues) { ScopedInRtl in_rtl; - Flags f = {}; + Flags f; f.enable_annotations = false; f.exitcode = -11; diff --git a/lib/tsan/unit_tests/tsan_mman_test.cc b/lib/tsan/tests/unit/tsan_mman_test.cc index 1a9a88f606fc..1a9a88f606fc 100644 --- a/lib/tsan/unit_tests/tsan_mman_test.cc +++ b/lib/tsan/tests/unit/tsan_mman_test.cc diff --git a/lib/tsan/unit_tests/tsan_mutex_test.cc b/lib/tsan/tests/unit/tsan_mutex_test.cc index c39841ddcbb1..c39841ddcbb1 100644 --- a/lib/tsan/unit_tests/tsan_mutex_test.cc +++ b/lib/tsan/tests/unit/tsan_mutex_test.cc diff --git a/lib/tsan/tests/unit/tsan_mutexset_test.cc b/lib/tsan/tests/unit/tsan_mutexset_test.cc new file mode 100644 index 000000000000..da1ae2e49e0c --- /dev/null +++ b/lib/tsan/tests/unit/tsan_mutexset_test.cc @@ -0,0 +1,126 @@ +//===-- tsan_mutexset_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 (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "tsan_mutexset.h" +#include "gtest/gtest.h" + +namespace __tsan { + +static void Expect(const MutexSet &mset, uptr i, u64 id, bool write, u64 epoch, + int count) { + MutexSet::Desc d = mset.Get(i); + EXPECT_EQ(id, d.id); + EXPECT_EQ(write, d.write); + EXPECT_EQ(epoch, d.epoch); + EXPECT_EQ(count, d.count); +} + +TEST(MutexSet, Basic) { + MutexSet mset; + EXPECT_EQ(mset.Size(), (uptr)0); + + mset.Add(1, true, 2); + EXPECT_EQ(mset.Size(), (uptr)1); + Expect(mset, 0, 1, true, 2, 1); + mset.Del(1, true); + EXPECT_EQ(mset.Size(), (uptr)0); + + mset.Add(3, true, 4); + mset.Add(5, false, 6); + EXPECT_EQ(mset.Size(), (uptr)2); + Expect(mset, 0, 3, true, 4, 1); + Expect(mset, 1, 5, false, 6, 1); + mset.Del(3, true); + EXPECT_EQ(mset.Size(), (uptr)1); + mset.Del(5, false); + EXPECT_EQ(mset.Size(), (uptr)0); +} + +TEST(MutexSet, DoubleAdd) { + MutexSet mset; + mset.Add(1, true, 2); + EXPECT_EQ(mset.Size(), (uptr)1); + Expect(mset, 0, 1, true, 2, 1); + + mset.Add(1, true, 2); + EXPECT_EQ(mset.Size(), (uptr)1); + Expect(mset, 0, 1, true, 2, 2); + + mset.Del(1, true); + EXPECT_EQ(mset.Size(), (uptr)1); + Expect(mset, 0, 1, true, 2, 1); + + mset.Del(1, true); + EXPECT_EQ(mset.Size(), (uptr)0); +} + +TEST(MutexSet, DoubleDel) { + MutexSet mset; + mset.Add(1, true, 2); + EXPECT_EQ(mset.Size(), (uptr)1); + mset.Del(1, true); + EXPECT_EQ(mset.Size(), (uptr)0); + mset.Del(1, true); + EXPECT_EQ(mset.Size(), (uptr)0); +} + +TEST(MutexSet, Remove) { + MutexSet mset; + mset.Add(1, true, 2); + mset.Add(1, true, 2); + mset.Add(3, true, 4); + mset.Add(3, true, 4); + EXPECT_EQ(mset.Size(), (uptr)2); + + mset.Remove(1); + EXPECT_EQ(mset.Size(), (uptr)1); + Expect(mset, 0, 3, true, 4, 2); +} + +TEST(MutexSet, Full) { + MutexSet mset; + for (uptr i = 0; i < MutexSet::kMaxSize; i++) { + mset.Add(i, true, i + 1); + } + EXPECT_EQ(mset.Size(), MutexSet::kMaxSize); + for (uptr i = 0; i < MutexSet::kMaxSize; i++) { + Expect(mset, i, i, true, i + 1, 1); + } + + for (uptr i = 0; i < MutexSet::kMaxSize; i++) { + mset.Add(i, true, i + 1); + } + EXPECT_EQ(mset.Size(), MutexSet::kMaxSize); + for (uptr i = 0; i < MutexSet::kMaxSize; i++) { + Expect(mset, i, i, true, i + 1, 2); + } +} + +TEST(MutexSet, Overflow) { + MutexSet mset; + for (uptr i = 0; i < MutexSet::kMaxSize; i++) { + mset.Add(i, true, i + 1); + mset.Add(i, true, i + 1); + } + mset.Add(100, true, 200); + EXPECT_EQ(mset.Size(), MutexSet::kMaxSize); + for (uptr i = 0; i < MutexSet::kMaxSize; i++) { + if (i == 0) + Expect(mset, i, 63, true, 64, 2); + else if (i == MutexSet::kMaxSize - 1) + Expect(mset, i, 100, true, 200, 1); + else + Expect(mset, i, i, true, i + 1, 2); + } +} + +} // namespace __tsan diff --git a/lib/tsan/unit_tests/tsan_platform_test.cc b/lib/tsan/tests/unit/tsan_platform_test.cc index 64c4499fbeae..b43dbb4e4ff3 100644 --- a/lib/tsan/unit_tests/tsan_platform_test.cc +++ b/lib/tsan/tests/unit/tsan_platform_test.cc @@ -12,6 +12,7 @@ //===----------------------------------------------------------------------===// #include "sanitizer_common/sanitizer_libc.h" #include "tsan_platform.h" +#include "tsan_rtl.h" #include "gtest/gtest.h" namespace __tsan { diff --git a/lib/tsan/unit_tests/tsan_shadow_test.cc b/lib/tsan/tests/unit/tsan_shadow_test.cc index 41f9121be0fa..fa9c982c0f6d 100644 --- a/lib/tsan/unit_tests/tsan_shadow_test.cc +++ b/lib/tsan/tests/unit/tsan_shadow_test.cc @@ -11,10 +11,41 @@ // //===----------------------------------------------------------------------===// #include "tsan_platform.h" +#include "tsan_rtl.h" #include "gtest/gtest.h" namespace __tsan { +TEST(Shadow, FastState) { + Shadow s(FastState(11, 22)); + EXPECT_EQ(s.tid(), (u64)11); + EXPECT_EQ(s.epoch(), (u64)22); + EXPECT_EQ(s.GetIgnoreBit(), false); + EXPECT_EQ(s.GetFreedAndReset(), false); + EXPECT_EQ(s.GetHistorySize(), 0); + EXPECT_EQ(s.addr0(), (u64)0); + EXPECT_EQ(s.size(), (u64)1); + EXPECT_EQ(s.is_write(), false); + + s.IncrementEpoch(); + EXPECT_EQ(s.epoch(), (u64)23); + s.IncrementEpoch(); + EXPECT_EQ(s.epoch(), (u64)24); + + s.SetIgnoreBit(); + EXPECT_EQ(s.GetIgnoreBit(), true); + s.ClearIgnoreBit(); + EXPECT_EQ(s.GetIgnoreBit(), false); + + for (int i = 0; i < 8; i++) { + s.SetHistorySize(i); + EXPECT_EQ(s.GetHistorySize(), i); + } + s.SetHistorySize(2); + s.ClearHistorySize(); + EXPECT_EQ(s.GetHistorySize(), 0); +} + TEST(Shadow, Mapping) { static int global; int stack; diff --git a/lib/tsan/tests/unit/tsan_stack_test.cc b/lib/tsan/tests/unit/tsan_stack_test.cc new file mode 100644 index 000000000000..d5392959c48c --- /dev/null +++ b/lib/tsan/tests/unit/tsan_stack_test.cc @@ -0,0 +1,80 @@ +//===-- tsan_stack_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 (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "tsan_sync.h" +#include "tsan_rtl.h" +#include "gtest/gtest.h" +#include <string.h> + +namespace __tsan { + +static void TestStackTrace(StackTrace *trace) { + ThreadState thr(0, 0, 0, 0, 0, 0, 0, 0); + + trace->ObtainCurrent(&thr, 0); + EXPECT_EQ(trace->Size(), (uptr)0); + + trace->ObtainCurrent(&thr, 42); + EXPECT_EQ(trace->Size(), (uptr)1); + EXPECT_EQ(trace->Get(0), (uptr)42); + + *thr.shadow_stack_pos++ = 100; + *thr.shadow_stack_pos++ = 101; + trace->ObtainCurrent(&thr, 0); + EXPECT_EQ(trace->Size(), (uptr)2); + EXPECT_EQ(trace->Get(0), (uptr)100); + EXPECT_EQ(trace->Get(1), (uptr)101); + + trace->ObtainCurrent(&thr, 42); + EXPECT_EQ(trace->Size(), (uptr)3); + EXPECT_EQ(trace->Get(0), (uptr)100); + EXPECT_EQ(trace->Get(1), (uptr)101); + EXPECT_EQ(trace->Get(2), (uptr)42); +} + +TEST(StackTrace, Basic) { + ScopedInRtl in_rtl; + StackTrace trace; + TestStackTrace(&trace); +} + +TEST(StackTrace, StaticBasic) { + ScopedInRtl in_rtl; + uptr buf[10]; + StackTrace trace1(buf, 10); + TestStackTrace(&trace1); + StackTrace trace2(buf, 3); + TestStackTrace(&trace2); +} + +TEST(StackTrace, StaticTrim) { + ScopedInRtl in_rtl; + uptr buf[2]; + StackTrace trace(buf, 2); + ThreadState thr(0, 0, 0, 0, 0, 0, 0, 0); + + *thr.shadow_stack_pos++ = 100; + *thr.shadow_stack_pos++ = 101; + *thr.shadow_stack_pos++ = 102; + trace.ObtainCurrent(&thr, 0); + EXPECT_EQ(trace.Size(), (uptr)2); + EXPECT_EQ(trace.Get(0), (uptr)101); + EXPECT_EQ(trace.Get(1), (uptr)102); + + trace.ObtainCurrent(&thr, 42); + EXPECT_EQ(trace.Size(), (uptr)2); + EXPECT_EQ(trace.Get(0), (uptr)102); + EXPECT_EQ(trace.Get(1), (uptr)42); +} + + +} // namespace __tsan diff --git a/lib/tsan/unit_tests/tsan_suppressions_test.cc b/lib/tsan/tests/unit/tsan_suppressions_test.cc index e1e0c12c004c..e1e0c12c004c 100644 --- a/lib/tsan/unit_tests/tsan_suppressions_test.cc +++ b/lib/tsan/tests/unit/tsan_suppressions_test.cc diff --git a/lib/tsan/unit_tests/tsan_sync_test.cc b/lib/tsan/tests/unit/tsan_sync_test.cc index b7605a57a331..dddf0b290883 100644 --- a/lib/tsan/unit_tests/tsan_sync_test.cc +++ b/lib/tsan/tests/unit/tsan_sync_test.cc @@ -36,7 +36,7 @@ TEST(Sync, Table) { uintptr_t addr = rand_r(&seed) % (kRange - 1) + 1; if (rand_r(&seed) % 2) { // Get or add. - SyncVar *v = tab.GetAndLock(thr, pc, addr, true); + SyncVar *v = tab.GetOrCreateAndLock(thr, pc, addr, true); EXPECT_TRUE(golden[addr] == 0 || golden[addr] == v); EXPECT_EQ(v->addr, addr); golden[addr] = v; diff --git a/lib/tsan/unit_tests/tsan_vector_test.cc b/lib/tsan/tests/unit/tsan_vector_test.cc index cfef6e528ff2..cfef6e528ff2 100644 --- a/lib/tsan/unit_tests/tsan_vector_test.cc +++ b/lib/tsan/tests/unit/tsan_vector_test.cc diff --git a/lib/ubsan/CMakeLists.txt b/lib/ubsan/CMakeLists.txt new file mode 100644 index 000000000000..40d0e897179d --- /dev/null +++ b/lib/ubsan/CMakeLists.txt @@ -0,0 +1,49 @@ +# Build for the undefined behavior sanitizer runtime support library. + +set(UBSAN_SOURCES + ubsan_diag.cc + ubsan_handlers.cc + ubsan_handlers_cxx.cc + ubsan_type_hash.cc + ubsan_value.cc + ) + +include_directories(..) + +set(UBSAN_CFLAGS ${SANITIZER_COMMON_CFLAGS}) + +filter_available_targets(UBSAN_SUPPORTED_ARCH + x86_64 i386) + +set(UBSAN_RUNTIME_LIBRARIES) + +if(APPLE) + # Build universal binary on APPLE. + add_library(clang_rt.ubsan_osx STATIC + ${UBSAN_SOURCES} + $<TARGET_OBJECTS:RTSanitizerCommon.osx> + ) + set_target_compile_flags(clang_rt.ubsan_osx ${UBSAN_CFLAGS}) + set_target_properties(clang_rt.ubsan_osx PROPERTIES + OSX_ARCHITECTURES "${UBSAN_SUPPORTED_ARCH}") + list(APPEND UBSAN_RUNTIME_LIBRARIES clang_rt.ubsan_osx) +else() + # Build separate libraries for each target. + foreach(arch ${UBSAN_SUPPORTED_ARCH}) + add_library(clang_rt.ubsan-${arch} STATIC + ${UBSAN_SOURCES} + $<TARGET_OBJECTS:RTSanitizerCommon.${arch}> + ) + set_target_compile_flags(clang_rt.ubsan-${arch} + ${UBSAN_CFLAGS} ${TARGET_${arch}_CFLAGS} + ) + list(APPEND UBSAN_RUNTIME_LIBRARIES clang_rt.ubsan-${arch}) + endforeach() +endif() + + +set_property(TARGET ${UBSAN_RUNTIME_LIBRARIES} APPEND PROPERTY + COMPILE_DEFINITIONS ${UBSAN_COMMON_DEFINITIONS}) +add_clang_compiler_rt_libraries(${UBSAN_RUNTIME_LIBRARIES}) + +add_subdirectory(lit_tests) diff --git a/lib/ubsan/Makefile.mk b/lib/ubsan/Makefile.mk new file mode 100644 index 000000000000..5702e0e752d8 --- /dev/null +++ b/lib/ubsan/Makefile.mk @@ -0,0 +1,23 @@ +#===- lib/ubsan/Makefile.mk ---------------------------------*- Makefile -*--===# +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +#===------------------------------------------------------------------------===# + +ModuleName := ubsan +SubDirs := + +Sources := $(foreach file,$(wildcard $(Dir)/*.cc),$(notdir $(file))) +ObjNames := $(Sources:%.cc=%.o) + +Implementation := Generic + +# FIXME: use automatic dependencies? +Dependencies := $(wildcard $(Dir)/*.h) +Dependencies += $(wildcard $(Dir)/../sanitizer_common/*.h) + +# Define a convenience variable for all the ubsan functions. +UbsanFunctions := $(Sources:%.cc=%) diff --git a/lib/ubsan/lit_tests/CMakeLists.txt b/lib/ubsan/lit_tests/CMakeLists.txt new file mode 100644 index 000000000000..565c523ceb49 --- /dev/null +++ b/lib/ubsan/lit_tests/CMakeLists.txt @@ -0,0 +1,22 @@ +configure_lit_site_cfg( + ${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.in + ${CMAKE_CURRENT_BINARY_DIR}/lit.site.cfg + ) + +if(COMPILER_RT_CAN_EXECUTE_TESTS) + # Run UBSan output tests only if we're sure that clang would produce + # working binaries. + set(UBSAN_TEST_DEPS + clang clang-headers FileCheck count not + ${UBSAN_RUNTIME_LIBRARIES} + ) + set(UBSAN_TEST_PARAMS + ubsan_site_config=${CMAKE_CURRENT_BINARY_DIR}/lit.site.cfg + ) + add_lit_testsuite(check-ubsan "Running UndefinedBehaviorSanitizer tests" + ${CMAKE_CURRENT_BINARY_DIR} + PARAMS ${UBSAN_TEST_PARAMS} + DEPENDS ${UBSAN_TEST_DEPS} + ) + set_target_properties(check-ubsan PROPERTIES FOLDER "UBSan unittests") +endif() diff --git a/lib/ubsan/lit_tests/Float/cast-overflow.cpp b/lib/ubsan/lit_tests/Float/cast-overflow.cpp new file mode 100644 index 000000000000..63410dc87140 --- /dev/null +++ b/lib/ubsan/lit_tests/Float/cast-overflow.cpp @@ -0,0 +1,98 @@ +// RUN: %clang -fsanitize=float-cast-overflow %s -o %t +// RUN: %t _ +// RUN: %t 0 2>&1 | FileCheck %s --check-prefix=CHECK-0 +// RUN: %t 1 2>&1 | FileCheck %s --check-prefix=CHECK-1 +// RUN: %t 2 2>&1 | FileCheck %s --check-prefix=CHECK-2 +// RUN: %t 3 2>&1 | FileCheck %s --check-prefix=CHECK-3 +// RUN: %t 4 2>&1 | FileCheck %s --check-prefix=CHECK-4 +// RUN: %t 5 2>&1 | FileCheck %s --check-prefix=CHECK-5 +// RUN: %t 6 2>&1 | FileCheck %s --check-prefix=CHECK-6 +// FIXME: %t 7 2>&1 | FileCheck %s --check-prefix=CHECK-7 +// RUN: %t 8 2>&1 | FileCheck %s --check-prefix=CHECK-8 +// RUN: %t 9 2>&1 | FileCheck %s --check-prefix=CHECK-9 + +// This test assumes float and double are IEEE-754 single- and double-precision. + +#include <stdint.h> +#include <stdio.h> +#include <string.h> + +float Inf; +float NaN; + +int main(int argc, char **argv) { + float MaxFloatRepresentableAsInt = 0x7fffff80; + (int)MaxFloatRepresentableAsInt; // ok + (int)-MaxFloatRepresentableAsInt; // ok + + float MinFloatRepresentableAsInt = -0x7fffffff - 1; + (int)MinFloatRepresentableAsInt; // ok + + float MaxFloatRepresentableAsUInt = 0xffffff00u; + (unsigned int)MaxFloatRepresentableAsUInt; // ok + +#ifdef __SIZEOF_INT128__ + unsigned __int128 FloatMaxAsUInt128 = -((unsigned __int128)1 << 104); + (void)(float)FloatMaxAsUInt128; // ok +#endif + + // Build a '+Inf'. + char InfVal[] = { 0x00, 0x00, 0x80, 0x7f }; + float Inf; + memcpy(&Inf, InfVal, 4); + + // Build a 'NaN'. + char NaNVal[] = { 0x01, 0x00, 0x80, 0x7f }; + float NaN; + memcpy(&NaN, NaNVal, 4); + + switch (argv[1][0]) { + // FIXME: Produce a source location for these checks and test for it here. + + // Floating point -> integer overflow. + case '0': + // Note that values between 0x7ffffe00 and 0x80000000 may or may not + // successfully round-trip, depending on the rounding mode. + // CHECK-0: runtime error: value 2.14748{{.*}} is outside the range of representable values of type 'int' + return MaxFloatRepresentableAsInt + 0x80; + case '1': + // CHECK-1: runtime error: value -2.14748{{.*}} is outside the range of representable values of type 'int' + return MinFloatRepresentableAsInt - 0x100; + case '2': + // CHECK-2: runtime error: value -0.001 is outside the range of representable values of type 'unsigned int' + return (unsigned)-0.001; + case '3': + // CHECK-3: runtime error: value 4.2949{{.*}} is outside the range of representable values of type 'unsigned int' + return (unsigned)(MaxFloatRepresentableAsUInt + 0x100); + + case '4': + // CHECK-4: runtime error: value {{.*}} is outside the range of representable values of type 'int' + return Inf; + case '5': + // CHECK-5: runtime error: value {{.*}} is outside the range of representable values of type 'int' + return NaN; + + // Integer -> floating point overflow. + case '6': + // CHECK-6: {{runtime error: value 0xffffff00000000000000000000000001 is outside the range of representable values of type 'float'|__int128 not supported}} +#ifdef __SIZEOF_INT128__ + return (float)(FloatMaxAsUInt128 + 1); +#else + puts("__int128 not supported"); + return 0; +#endif + // FIXME: The backend cannot lower __fp16 operations on x86 yet. + //case '7': + // (__fp16)65504; // ok + // // CHECK-7: runtime error: value 65505 is outside the range of representable values of type '__fp16' + // return (__fp16)65505; + + // Floating point -> floating point overflow. + case '8': + // CHECK-8: runtime error: value 1e+39 is outside the range of representable values of type 'float' + return (float)1e39; + case '9': + // CHECK-9: runtime error: value {{.*}} is outside the range of representable values of type 'double' + return (double)Inf; + } +} diff --git a/lib/ubsan/lit_tests/Integer/add-overflow.cpp b/lib/ubsan/lit_tests/Integer/add-overflow.cpp new file mode 100644 index 000000000000..80543524f51d --- /dev/null +++ b/lib/ubsan/lit_tests/Integer/add-overflow.cpp @@ -0,0 +1,32 @@ +// RUN: %clang -DADD_I32 -fsanitize=signed-integer-overflow %s -o %t && %t 2>&1 | FileCheck %s --check-prefix=ADD_I32 +// RUN: %clang -DADD_I64 -fsanitize=signed-integer-overflow %s -o %t && %t 2>&1 | FileCheck %s --check-prefix=ADD_I64 +// RUN: %clang -DADD_I128 -fsanitize=signed-integer-overflow %s -o %t && %t 2>&1 | FileCheck %s --check-prefix=ADD_I128 + +#include <stdint.h> +#include <stdio.h> + +int main() { + // These promote to 'int'. + (void)(int8_t(0x7f) + int8_t(0x7f)); + (void)(int16_t(0x3fff) + int16_t(0x4000)); + +#ifdef ADD_I32 + int32_t k = 0x12345678; + k += 0x789abcde; + // CHECK-ADD_I32: add-overflow.cpp:[[@LINE-1]]:5: runtime error: signed integer overflow: 305419896 + 2023406814 cannot be represented in type 'int' +#endif + +#ifdef ADD_I64 + (void)(int64_t(8000000000000000000ll) + int64_t(2000000000000000000ll)); + // CHECK-ADD_I64: 8000000000000000000 + 2000000000000000000 cannot be represented in type '{{long( long)?}}' +#endif + +#ifdef ADD_I128 +# ifdef __SIZEOF_INT128__ + (void)((__int128_t(1) << 126) + (__int128_t(1) << 126)); +# else + puts("__int128 not supported"); +# endif + // CHECK-ADD_I128: {{0x40000000000000000000000000000000 \+ 0x40000000000000000000000000000000 cannot be represented in type '__int128'|__int128 not supported}} +#endif +} diff --git a/lib/ubsan/lit_tests/Integer/div-overflow.cpp b/lib/ubsan/lit_tests/Integer/div-overflow.cpp new file mode 100644 index 000000000000..dd82427f9d5b --- /dev/null +++ b/lib/ubsan/lit_tests/Integer/div-overflow.cpp @@ -0,0 +1,10 @@ +// RUN: %clang -fsanitize=signed-integer-overflow %s -o %t && %t 2>&1 | FileCheck %s + +#include <stdint.h> + +int main() { + unsigned(0x80000000) / -1; + + // CHECK: div-overflow.cpp:9:23: runtime error: division of -2147483648 by -1 cannot be represented in type 'int' + int32_t(0x80000000) / -1; +} diff --git a/lib/ubsan/lit_tests/Integer/div-zero.cpp b/lib/ubsan/lit_tests/Integer/div-zero.cpp new file mode 100644 index 000000000000..b2a839566c5f --- /dev/null +++ b/lib/ubsan/lit_tests/Integer/div-zero.cpp @@ -0,0 +1,15 @@ +// RUN: %clang -fsanitize=integer-divide-by-zero -DDIVIDEND=0 %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clang -fsanitize=integer-divide-by-zero -DDIVIDEND=1U %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clang -fsanitize=float-divide-by-zero -DDIVIDEND=1.5 %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clang -fsanitize=integer-divide-by-zero -DDIVIDEND='intmax(123)' %s -o %t && %t 2>&1 | FileCheck %s + +#ifdef __SIZEOF_INT128__ +typedef __int128 intmax; +#else +typedef long long intmax; +#endif + +int main() { + // CHECK: div-zero.cpp:[[@LINE+1]]:12: runtime error: division by zero + DIVIDEND / 0; +} diff --git a/lib/ubsan/lit_tests/Integer/incdec-overflow.cpp b/lib/ubsan/lit_tests/Integer/incdec-overflow.cpp new file mode 100644 index 000000000000..48b68b6365c6 --- /dev/null +++ b/lib/ubsan/lit_tests/Integer/incdec-overflow.cpp @@ -0,0 +1,16 @@ +// RUN: %clang -DOP=n++ -fsanitize=signed-integer-overflow %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clang -DOP=++n -fsanitize=signed-integer-overflow %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clang -DOP=m-- -fsanitize=signed-integer-overflow %s -o %t && %t 2>&1 | FileCheck %s +// RUN: %clang -DOP=--m -fsanitize=signed-integer-overflow %s -o %t && %t 2>&1 | FileCheck %s + +#include <stdint.h> + +int main() { + int n = 0x7ffffffd; + n++; + n++; + int m = -n - 1; + // CHECK: incdec-overflow.cpp:15:3: runtime error: signed integer overflow: [[MINUS:-?]]214748364 + // CHECK: + [[MINUS]]1 cannot be represented in type 'int' + OP; +} diff --git a/lib/ubsan/lit_tests/Integer/mul-overflow.cpp b/lib/ubsan/lit_tests/Integer/mul-overflow.cpp new file mode 100644 index 000000000000..8d1e70d6ad48 --- /dev/null +++ b/lib/ubsan/lit_tests/Integer/mul-overflow.cpp @@ -0,0 +1,14 @@ +// RUN: %clang -fsanitize=signed-integer-overflow %s -o %t && %t 2>&1 | FileCheck %s + +#include <stdint.h> + +int main() { + // These promote to 'int'. + (void)(int8_t(-2) * int8_t(0x7f)); + (void)(int16_t(0x7fff) * int16_t(0x7fff)); + (void)(uint16_t(0xffff) * int16_t(0x7fff)); + (void)(uint16_t(0xffff) * uint16_t(0x8000)); + + // CHECK: mul-overflow.cpp:13:27: runtime error: signed integer overflow: 65535 * 32769 cannot be represented in type 'int' + (void)(uint16_t(0xffff) * uint16_t(0x8001)); +} diff --git a/lib/ubsan/lit_tests/Integer/negate-overflow.cpp b/lib/ubsan/lit_tests/Integer/negate-overflow.cpp new file mode 100644 index 000000000000..2ee4f10115e8 --- /dev/null +++ b/lib/ubsan/lit_tests/Integer/negate-overflow.cpp @@ -0,0 +1,12 @@ +// RUN: %clang -fsanitize=signed-integer-overflow %s -o %t && %t 2>&1 | FileCheck %s --check-prefix=CHECKS +// RUN: %clang -fsanitize=unsigned-integer-overflow %s -o %t && %t 2>&1 | FileCheck %s --check-prefix=CHECKU + +int main() { + // CHECKS-NOT: runtime error + // CHECKU: negate-overflow.cpp:[[@LINE+2]]:3: runtime error: negation of 2147483648 cannot be represented in type 'unsigned int' + // CHECKU-NOT: cast to an unsigned + -unsigned(-0x7fffffff - 1); // ok + // CHECKS: negate-overflow.cpp:[[@LINE+2]]:10: runtime error: negation of -2147483648 cannot be represented in type 'int'; cast to an unsigned type to negate this value to itself + // CHECKU-NOT: runtime error + return -(-0x7fffffff - 1); +} diff --git a/lib/ubsan/lit_tests/Integer/no-recover.cpp b/lib/ubsan/lit_tests/Integer/no-recover.cpp new file mode 100644 index 000000000000..e200feaa79ae --- /dev/null +++ b/lib/ubsan/lit_tests/Integer/no-recover.cpp @@ -0,0 +1,22 @@ +// RUN: %clang -fsanitize=unsigned-integer-overflow %s -o %t && %t 2>&1 | FileCheck %s --check-prefix=RECOVER +// RUN: %clang -fsanitize=unsigned-integer-overflow -fsanitize-recover %s -o %t && %t 2>&1 | FileCheck %s --check-prefix=RECOVER +// RUN: %clang -fsanitize=unsigned-integer-overflow -fno-sanitize-recover %s -o %t && %t 2>&1 | FileCheck %s --check-prefix=ABORT + +#include <stdint.h> + +int main() { + // These promote to 'int'. + (void)(uint8_t(0xff) + uint8_t(0xff)); + (void)(uint16_t(0xf0fff) + uint16_t(0x0fff)); + // RECOVER-NOT: runtime error + // ABORT-NOT: runtime error + + uint32_t k = 0x87654321; + k += 0xedcba987; + // RECOVER: no-recover.cpp:[[@LINE-1]]:5: runtime error: unsigned integer overflow: 2271560481 + 3989547399 cannot be represented in type 'unsigned int' + // ABORT: no-recover.cpp:[[@LINE-2]]:5: runtime error: unsigned integer overflow: 2271560481 + 3989547399 cannot be represented in type 'unsigned int' + + (void)(uint64_t(10000000000000000000ull) + uint64_t(9000000000000000000ull)); + // RECOVER: 10000000000000000000 + 9000000000000000000 cannot be represented in type 'unsigned long' + // ABORT-NOT: runtime error +} diff --git a/lib/ubsan/lit_tests/Integer/shift.cpp b/lib/ubsan/lit_tests/Integer/shift.cpp new file mode 100644 index 000000000000..19101c53e75e --- /dev/null +++ b/lib/ubsan/lit_tests/Integer/shift.cpp @@ -0,0 +1,37 @@ +// RUN: %clang -DLSH_OVERFLOW -DOP='<<' -fsanitize=shift %s -o %t && %t 2>&1 | FileCheck %s --check-prefix=LSH_OVERFLOW +// RUN: %clang -DLSH_OVERFLOW -DOP='<<=' -fsanitize=shift %s -o %t && %t 2>&1 | FileCheck %s --check-prefix=LSH_OVERFLOW +// RUN: %clang -DTOO_LOW -DOP='<<' -fsanitize=shift %s -o %t && %t 2>&1 | FileCheck %s --check-prefix=TOO_LOW +// RUN: %clang -DTOO_LOW -DOP='>>' -fsanitize=shift %s -o %t && %t 2>&1 | FileCheck %s --check-prefix=TOO_LOW +// RUN: %clang -DTOO_LOW -DOP='<<=' -fsanitize=shift %s -o %t && %t 2>&1 | FileCheck %s --check-prefix=TOO_LOW +// RUN: %clang -DTOO_LOW -DOP='>>=' -fsanitize=shift %s -o %t && %t 2>&1 | FileCheck %s --check-prefix=TOO_LOW +// RUN: %clang -DTOO_HIGH -DOP='<<' -fsanitize=shift %s -o %t && %t 2>&1 | FileCheck %s --check-prefix=TOO_HIGH +// RUN: %clang -DTOO_HIGH -DOP='>>' -fsanitize=shift %s -o %t && %t 2>&1 | FileCheck %s --check-prefix=TOO_HIGH +// RUN: %clang -DTOO_HIGH -DOP='<<=' -fsanitize=shift %s -o %t && %t 2>&1 | FileCheck %s --check-prefix=TOO_HIGH +// RUN: %clang -DTOO_HIGH -DOP='>>=' -fsanitize=shift %s -o %t && %t 2>&1 | FileCheck %s --check-prefix=TOO_HIGH + +#include <stdint.h> + +int main() { + int a = 1; + unsigned b = 1; + + a <<= 31; // ok in C++11, not ok in C99/C11 + b <<= 31; // ok + b <<= 1; // still ok, unsigned + +#ifdef LSH_OVERFLOW + // CHECK-LSH_OVERFLOW: shift.cpp:24:5: runtime error: left shift of negative value -2147483648 + a OP 1; +#endif + +#ifdef TOO_LOW + // CHECK-TOO_LOW: shift.cpp:29:5: runtime error: shift exponent -3 is negative + a OP (-3); +#endif + +#ifdef TOO_HIGH + a = 0; + // CHECK-TOO_HIGH: shift.cpp:35:5: runtime error: shift exponent 32 is too large for 32-bit type 'int' + a OP 32; +#endif +} diff --git a/lib/ubsan/lit_tests/Integer/sub-overflow.cpp b/lib/ubsan/lit_tests/Integer/sub-overflow.cpp new file mode 100644 index 000000000000..b43a69bee4e6 --- /dev/null +++ b/lib/ubsan/lit_tests/Integer/sub-overflow.cpp @@ -0,0 +1,31 @@ +// RUN: %clang -DSUB_I32 -fsanitize=signed-integer-overflow %s -o %t && %t 2>&1 | FileCheck %s --check-prefix=SUB_I32 +// RUN: %clang -DSUB_I64 -fsanitize=signed-integer-overflow %s -o %t && %t 2>&1 | FileCheck %s --check-prefix=SUB_I64 +// RUN: %clang -DSUB_I128 -fsanitize=signed-integer-overflow %s -o %t && %t 2>&1 | FileCheck %s --check-prefix=SUB_I128 + +#include <stdint.h> +#include <stdio.h> + +int main() { + // These promote to 'int'. + (void)(int8_t(-2) - int8_t(0x7f)); + (void)(int16_t(-2) - int16_t(0x7fff)); + +#ifdef SUB_I32 + (void)(int32_t(-2) - int32_t(0x7fffffff)); + // CHECK-SUB_I32: sub-overflow.cpp:[[@LINE-1]]:22: runtime error: signed integer overflow: -2 - 2147483647 cannot be represented in type 'int' +#endif + +#ifdef SUB_I64 + (void)(int64_t(-8000000000000000000ll) - int64_t(2000000000000000000ll)); + // CHECK-SUB_I64: -8000000000000000000 - 2000000000000000000 cannot be represented in type '{{long( long)?}}' +#endif + +#ifdef SUB_I128 +# ifdef __SIZEOF_INT128__ + (void)(-(__int128_t(1) << 126) - (__int128_t(1) << 126) - 1); +# else + puts("__int128 not supported"); +# endif + // CHECK-SUB_I128: {{0x80000000000000000000000000000000 - 1 cannot be represented in type '__int128'|__int128 not supported}} +#endif +} diff --git a/lib/ubsan/lit_tests/Integer/uadd-overflow.cpp b/lib/ubsan/lit_tests/Integer/uadd-overflow.cpp new file mode 100644 index 000000000000..0edb10092e2c --- /dev/null +++ b/lib/ubsan/lit_tests/Integer/uadd-overflow.cpp @@ -0,0 +1,32 @@ +// RUN: %clang -DADD_I32 -fsanitize=unsigned-integer-overflow %s -o %t && %t 2>&1 | FileCheck %s --check-prefix=ADD_I32 +// RUN: %clang -DADD_I64 -fsanitize=unsigned-integer-overflow %s -o %t && %t 2>&1 | FileCheck %s --check-prefix=ADD_I64 +// RUN: %clang -DADD_I128 -fsanitize=unsigned-integer-overflow %s -o %t && %t 2>&1 | FileCheck %s --check-prefix=ADD_I128 + +#include <stdint.h> +#include <stdio.h> + +int main() { + // These promote to 'int'. + (void)(uint8_t(0xff) + uint8_t(0xff)); + (void)(uint16_t(0xf0fff) + uint16_t(0x0fff)); + +#ifdef ADD_I32 + uint32_t k = 0x87654321; + k += 0xedcba987; + // CHECK-ADD_I32: uadd-overflow.cpp:[[@LINE-1]]:5: runtime error: unsigned integer overflow: 2271560481 + 3989547399 cannot be represented in type 'unsigned int' +#endif + +#ifdef ADD_I64 + (void)(uint64_t(10000000000000000000ull) + uint64_t(9000000000000000000ull)); + // CHECK-ADD_I64: 10000000000000000000 + 9000000000000000000 cannot be represented in type 'unsigned long' +#endif + +#ifdef ADD_I128 +# ifdef __SIZEOF_INT128__ + (void)((__uint128_t(1) << 127) + (__uint128_t(1) << 127)); +# else + puts("__int128 not supported"); +# endif + // CHECK-ADD_I128: {{0x80000000000000000000000000000000 \+ 0x80000000000000000000000000000000 cannot be represented in type 'unsigned __int128'|__int128 not supported}} +#endif +} diff --git a/lib/ubsan/lit_tests/Integer/uincdec-overflow.cpp b/lib/ubsan/lit_tests/Integer/uincdec-overflow.cpp new file mode 100644 index 000000000000..6b677ca5bd35 --- /dev/null +++ b/lib/ubsan/lit_tests/Integer/uincdec-overflow.cpp @@ -0,0 +1,16 @@ +// RUN: %clang -DOP=n++ -fsanitize=unsigned-integer-overflow %s -o %t && %t 2>&1 | FileCheck --check-prefix=INC %s +// RUN: %clang -DOP=++n -fsanitize=unsigned-integer-overflow %s -o %t && %t 2>&1 | FileCheck --check-prefix=INC %s +// RUN: %clang -DOP=m-- -fsanitize=unsigned-integer-overflow %s -o %t && %t 2>&1 | FileCheck --check-prefix=DEC %s +// RUN: %clang -DOP=--m -fsanitize=unsigned-integer-overflow %s -o %t && %t 2>&1 | FileCheck --check-prefix=DEC %s + +#include <stdint.h> + +int main() { + unsigned n = 0xfffffffd; + n++; + n++; + unsigned m = 0; + // CHECK-INC: uincdec-overflow.cpp:15:3: runtime error: unsigned integer overflow: 4294967295 + 1 cannot be represented in type 'unsigned int' + // CHECK-DEC: uincdec-overflow.cpp:15:3: runtime error: unsigned integer overflow: 0 - 1 cannot be represented in type 'unsigned int' + OP; +} diff --git a/lib/ubsan/lit_tests/Integer/umul-overflow.cpp b/lib/ubsan/lit_tests/Integer/umul-overflow.cpp new file mode 100644 index 000000000000..42cf3a780ed0 --- /dev/null +++ b/lib/ubsan/lit_tests/Integer/umul-overflow.cpp @@ -0,0 +1,19 @@ +// RUN: %clang -fsanitize=unsigned-integer-overflow %s -o %t && %t 2>&1 | FileCheck %s + +#include <stdint.h> + +int main() { + // These promote to 'int'. + (void)(int8_t(-2) * int8_t(0x7f)); + (void)(int16_t(0x7fff) * int16_t(0x7fff)); + (void)(uint16_t(0xffff) * int16_t(0x7fff)); + (void)(uint16_t(0xffff) * uint16_t(0x8000)); + + // Not an unsigned overflow + (void)(uint16_t(0xffff) * uint16_t(0x8001)); + + (void)(uint32_t(0xffffffff) * uint32_t(0x2)); + // CHECK: umul-overflow.cpp:15:31: runtime error: unsigned integer overflow: 4294967295 * 2 cannot be represented in type 'unsigned int' + + return 0; +} diff --git a/lib/ubsan/lit_tests/Integer/usub-overflow.cpp b/lib/ubsan/lit_tests/Integer/usub-overflow.cpp new file mode 100644 index 000000000000..357d662ad63e --- /dev/null +++ b/lib/ubsan/lit_tests/Integer/usub-overflow.cpp @@ -0,0 +1,31 @@ +// RUN: %clang -DSUB_I32 -fsanitize=unsigned-integer-overflow %s -o %t && %t 2>&1 | FileCheck %s --check-prefix=SUB_I32 +// RUN: %clang -DSUB_I64 -fsanitize=unsigned-integer-overflow %s -o %t && %t 2>&1 | FileCheck %s --check-prefix=SUB_I64 +// RUN: %clang -DSUB_I128 -fsanitize=unsigned-integer-overflow %s -o %t && %t 2>&1 | FileCheck %s --check-prefix=SUB_I128 + +#include <stdint.h> +#include <stdio.h> + +int main() { + // These promote to 'int'. + (void)(uint8_t(0) - uint8_t(0x7f)); + (void)(uint16_t(0) - uint16_t(0x7fff)); + +#ifdef SUB_I32 + (void)(uint32_t(1) - uint32_t(2)); + // CHECK-SUB_I32: usub-overflow.cpp:[[@LINE-1]]:22: runtime error: unsigned integer overflow: 1 - 2 cannot be represented in type 'unsigned int' +#endif + +#ifdef SUB_I64 + (void)(uint64_t(8000000000000000000ll) - uint64_t(9000000000000000000ll)); + // CHECK-SUB_I64: 8000000000000000000 - 9000000000000000000 cannot be represented in type 'unsigned long' +#endif + +#ifdef SUB_I128 +# ifdef __SIZEOF_INT128__ + (void)((__uint128_t(1) << 126) - (__uint128_t(1) << 127)); +# else + puts("__int128 not supported\n"); +# endif + // CHECK-SUB_I128: {{0x40000000000000000000000000000000 - 0x80000000000000000000000000000000 cannot be represented in type 'unsigned __int128'|__int128 not supported}} +#endif +} diff --git a/lib/ubsan/lit_tests/Misc/bool.cpp b/lib/ubsan/lit_tests/Misc/bool.cpp new file mode 100644 index 000000000000..8fafe7eac053 --- /dev/null +++ b/lib/ubsan/lit_tests/Misc/bool.cpp @@ -0,0 +1,11 @@ +// RUN: %clang -fsanitize=bool %s -O3 -o %T/bool.exe && %T/bool.exe 2>&1 | FileCheck %s + +unsigned char NotABool = 123; + +int main(int argc, char **argv) { + bool *p = (bool*)&NotABool; + + // FIXME: Provide a better source location here. + // CHECK: bool.exe:0x{{[0-9a-f]*}}: runtime error: load of value 123, which is not a valid value for type 'bool' + return *p; +} diff --git a/lib/ubsan/lit_tests/Misc/deduplication.cpp b/lib/ubsan/lit_tests/Misc/deduplication.cpp new file mode 100644 index 000000000000..d9c909f9af4f --- /dev/null +++ b/lib/ubsan/lit_tests/Misc/deduplication.cpp @@ -0,0 +1,25 @@ +// RUN: %clang -fsanitize=undefined %s -o %t && %t 2>&1 | FileCheck %s +// Verify deduplication works by ensuring only one diag is emitted. +#include <limits.h> +#include <stdio.h> + +void overflow() { + int i = INT_MIN; + --i; +} + +int main() { + // CHECK: Start + fprintf(stderr, "Start\n"); + + // CHECK: runtime error + // CHECK-NOT: runtime error + // CHECK-NOT: runtime error + overflow(); + overflow(); + overflow(); + + // CHECK: End + fprintf(stderr, "End\n"); + return 0; +} diff --git a/lib/ubsan/lit_tests/Misc/enum.cpp b/lib/ubsan/lit_tests/Misc/enum.cpp new file mode 100644 index 000000000000..b363fea3487f --- /dev/null +++ b/lib/ubsan/lit_tests/Misc/enum.cpp @@ -0,0 +1,17 @@ +// RUN: %clang -fsanitize=enum %s -O3 -o %t && %t 2>&1 | FileCheck %s --check-prefix=CHECK-PLAIN +// RUN: %clang -fsanitize=enum -std=c++11 -DE="class E" %s -O3 -o %t && %t +// RUN: %clang -fsanitize=enum -std=c++11 -DE="class E : bool" %s -O3 -o %t && %t 2>&1 | FileCheck %s --check-prefix=CHECK-BOOL + +enum E { a = 1 } e; +#undef E + +int main(int argc, char **argv) { + // memset(&e, 0xff, sizeof(e)); + for (unsigned char *p = (unsigned char*)&e; p != (unsigned char*)(&e + 1); ++p) + *p = 0xff; + + // CHECK-PLAIN: error: load of value 4294967295, which is not a valid value for type 'enum E' + // FIXME: Support marshalling and display of enum class values. + // CHECK-BOOL: error: load of value <unknown>, which is not a valid value for type 'enum E' + return (int)e != -1; +} diff --git a/lib/ubsan/lit_tests/Misc/missing_return.cpp b/lib/ubsan/lit_tests/Misc/missing_return.cpp new file mode 100644 index 000000000000..9997b8386f21 --- /dev/null +++ b/lib/ubsan/lit_tests/Misc/missing_return.cpp @@ -0,0 +1,9 @@ +// RUN: %clang -fsanitize=return %s -O3 -o %t && %t 2>&1 | FileCheck %s + +// CHECK: missing_return.cpp:4:5: runtime error: execution reached the end of a value-returning function without returning a value +int f() { +} + +int main(int, char **argv) { + return f(); +} diff --git a/lib/ubsan/lit_tests/Misc/unreachable.cpp b/lib/ubsan/lit_tests/Misc/unreachable.cpp new file mode 100644 index 000000000000..5ca4e5fd8b0c --- /dev/null +++ b/lib/ubsan/lit_tests/Misc/unreachable.cpp @@ -0,0 +1,6 @@ +// RUN: %clang -fsanitize=unreachable %s -O3 -o %t && %t 2>&1 | FileCheck %s + +int main(int, char **argv) { + // CHECK: unreachable.cpp:5:3: runtime error: execution reached a __builtin_unreachable() call + __builtin_unreachable(); +} diff --git a/lib/ubsan/lit_tests/Misc/vla.c b/lib/ubsan/lit_tests/Misc/vla.c new file mode 100644 index 000000000000..2fa88addc0d3 --- /dev/null +++ b/lib/ubsan/lit_tests/Misc/vla.c @@ -0,0 +1,11 @@ +// RUN: %clang -fsanitize=vla-bound %s -O3 -o %t +// RUN: %t 2>&1 | FileCheck %s --check-prefix=CHECK-MINUS-ONE +// RUN: %t a 2>&1 | FileCheck %s --check-prefix=CHECK-ZERO +// RUN: %t a b + +int main(int argc, char **argv) { + // CHECK-MINUS-ONE: vla.c:9:11: runtime error: variable length array bound evaluates to non-positive value -1 + // CHECK-ZERO: vla.c:9:11: runtime error: variable length array bound evaluates to non-positive value 0 + int arr[argc - 2]; + return 0; +} diff --git a/lib/ubsan/lit_tests/TypeCheck/misaligned.cpp b/lib/ubsan/lit_tests/TypeCheck/misaligned.cpp new file mode 100644 index 000000000000..3abacae8be89 --- /dev/null +++ b/lib/ubsan/lit_tests/TypeCheck/misaligned.cpp @@ -0,0 +1,73 @@ +// RUN: %clang -fsanitize=alignment %s -O3 -o %t +// RUN: %t l0 && %t s0 && %t r0 && %t m0 && %t f0 && %t n0 +// RUN: %t l1 2>&1 | FileCheck %s --check-prefix=CHECK-LOAD --strict-whitespace +// RUN: %t s1 2>&1 | FileCheck %s --check-prefix=CHECK-STORE +// RUN: %t r1 2>&1 | FileCheck %s --check-prefix=CHECK-REFERENCE +// RUN: %t m1 2>&1 | FileCheck %s --check-prefix=CHECK-MEMBER +// RUN: %t f1 2>&1 | FileCheck %s --check-prefix=CHECK-MEMFUN +// RUN: %t n1 2>&1 | FileCheck %s --check-prefix=CHECK-NEW + +#include <new> + +struct S { + S() {} + int f() { return 0; } + int k; +}; + +int main(int, char **argv) { + char c[] __attribute__((aligned(8))) = { 0, 0, 0, 0, 1, 2, 3, 4, 5 }; + + // Pointer value may be unspecified here, but behavior is not undefined. + int *p = (int*)&c[4 + argv[1][1] - '0']; + S *s = (S*)p; + + (void)*p; // ok! + + switch (argv[1][0]) { + case 'l': + // CHECK-LOAD: misaligned.cpp:[[@LINE+4]]:12: runtime error: load of misaligned address [[PTR:0x[0-9a-f]*]] for type 'int', which requires 4 byte alignment + // CHECK-LOAD-NEXT: [[PTR]]: note: pointer points here + // CHECK-LOAD-NEXT: {{^ 00 00 00 01 02 03 04 05}} + // CHECK-LOAD-NEXT: {{^ \^}} + return *p && 0; + + case 's': + // CHECK-STORE: misaligned.cpp:[[@LINE+4]]:5: runtime error: store to misaligned address [[PTR:0x[0-9a-f]*]] for type 'int', which requires 4 byte alignment + // CHECK-STORE-NEXT: [[PTR]]: note: pointer points here + // CHECK-STORE-NEXT: {{^ 00 00 00 01 02 03 04 05}} + // CHECK-STORE-NEXT: {{^ \^}} + *p = 1; + break; + + case 'r': + // CHECK-REFERENCE: misaligned.cpp:[[@LINE+4]]:15: runtime error: reference binding to misaligned address [[PTR:0x[0-9a-f]*]] for type 'int', which requires 4 byte alignment + // CHECK-REFERENCE-NEXT: [[PTR]]: note: pointer points here + // CHECK-REFERENCE-NEXT: {{^ 00 00 00 01 02 03 04 05}} + // CHECK-REFERENCE-NEXT: {{^ \^}} + {int &r = *p;} + break; + + case 'm': + // CHECK-MEMBER: misaligned.cpp:[[@LINE+4]]:15: runtime error: member access within misaligned address [[PTR:0x[0-9a-f]*]] for type 'S', which requires 4 byte alignment + // CHECK-MEMBER-NEXT: [[PTR]]: note: pointer points here + // CHECK-MEMBER-NEXT: {{^ 00 00 00 01 02 03 04 05}} + // CHECK-MEMBER-NEXT: {{^ \^}} + return s->k && 0; + + case 'f': + // CHECK-MEMFUN: misaligned.cpp:[[@LINE+4]]:12: runtime error: member call on misaligned address [[PTR:0x[0-9a-f]*]] for type 'S', which requires 4 byte alignment + // CHECK-MEMFUN-NEXT: [[PTR]]: note: pointer points here + // CHECK-MEMFUN-NEXT: {{^ 00 00 00 01 02 03 04 05}} + // CHECK-MEMFUN-NEXT: {{^ \^}} + return s->f() && 0; + + case 'n': + // FIXME: Provide a better source location here. + // CHECK-NEW: misaligned{{.*}}:0x{{[0-9a-f]*}}: runtime error: constructor call on misaligned address [[PTR:0x[0-9a-f]*]] for type 'S', which requires 4 byte alignment + // CHECK-NEW-NEXT: [[PTR]]: note: pointer points here + // CHECK-NEW-NEXT: {{^ 00 00 00 01 02 03 04 05}} + // CHECK-NEW-NEXT: {{^ \^}} + return (new (s) S)->k && 0; + } +} diff --git a/lib/ubsan/lit_tests/TypeCheck/null.cpp b/lib/ubsan/lit_tests/TypeCheck/null.cpp new file mode 100644 index 000000000000..f72af28ce160 --- /dev/null +++ b/lib/ubsan/lit_tests/TypeCheck/null.cpp @@ -0,0 +1,38 @@ +// RUN: %clang -fsanitize=null %s -O3 -o %t +// RUN: %t l 2>&1 | FileCheck %s --check-prefix=CHECK-LOAD +// RUN: %t s 2>&1 | FileCheck %s --check-prefix=CHECK-STORE +// RUN: %t r 2>&1 | FileCheck %s --check-prefix=CHECK-REFERENCE +// RUN: %t m 2>&1 | FileCheck %s --check-prefix=CHECK-MEMBER +// RUN: %t f 2>&1 | FileCheck %s --check-prefix=CHECK-MEMFUN + +struct S { + int f() { return 0; } + int k; +}; + +int main(int, char **argv) { + int *p = 0; + S *s = 0; + + (void)*p; // ok! + + switch (argv[1][0]) { + case 'l': + // CHECK-LOAD: null.cpp:22:12: runtime error: load of null pointer of type 'int' + return *p; + case 's': + // CHECK-STORE: null.cpp:25:5: runtime error: store to null pointer of type 'int' + *p = 1; + break; + case 'r': + // CHECK-REFERENCE: null.cpp:29:15: runtime error: reference binding to null pointer of type 'int' + {int &r = *p;} + break; + case 'm': + // CHECK-MEMBER: null.cpp:33:15: runtime error: member access within null pointer of type 'S' + return s->k; + case 'f': + // CHECK-MEMFUN: null.cpp:36:12: runtime error: member call on null pointer of type 'S' + return s->f(); + } +} diff --git a/lib/ubsan/lit_tests/TypeCheck/vptr.cpp b/lib/ubsan/lit_tests/TypeCheck/vptr.cpp new file mode 100644 index 000000000000..574a7bef9622 --- /dev/null +++ b/lib/ubsan/lit_tests/TypeCheck/vptr.cpp @@ -0,0 +1,106 @@ +// RUN: %clang -ccc-cxx -fsanitize=vptr %s -O3 -o %t +// RUN: %t rT && %t mT && %t fT +// RUN: %t rU && %t mU && %t fU +// RUN: %t rS && %t rV && %t oV +// RUN: %t mS 2>&1 | FileCheck %s --check-prefix=CHECK-MEMBER --strict-whitespace +// RUN: %t fS 2>&1 | FileCheck %s --check-prefix=CHECK-MEMFUN --strict-whitespace +// RUN: %t mV 2>&1 | FileCheck %s --check-prefix=CHECK-MEMBER --strict-whitespace +// RUN: %t fV 2>&1 | FileCheck %s --check-prefix=CHECK-MEMFUN --strict-whitespace +// RUN: %t oU 2>&1 | FileCheck %s --check-prefix=CHECK-OFFSET --strict-whitespace +// RUN: %t m0 2>&1 | FileCheck %s --check-prefix=CHECK-NULL-MEMBER --strict-whitespace + +// FIXME: This test produces linker errors on Darwin. +// XFAIL: darwin + +struct S { + S() : a(0) {} + ~S() {} + int a; + int f() { return 0; } + virtual int v() { return 0; } +}; + +struct T : S { + T() : b(0) {} + int b; + int g() { return 0; } + virtual int v() { return 1; } +}; + +struct U : S, T { virtual int v() { return 2; } }; + +int main(int, char **argv) { + T t; + (void)t.a; + (void)t.b; + (void)t.f(); + (void)t.g(); + (void)t.v(); + (void)t.S::v(); + + U u; + (void)u.T::a; + (void)u.b; + (void)u.T::f(); + (void)u.g(); + (void)u.v(); + (void)u.T::v(); + (void)((T&)u).S::v(); + + T *p = 0; + char Buffer[sizeof(U)] = {}; + switch (argv[1][1]) { + case '0': + p = reinterpret_cast<T*>(Buffer); + break; + case 'S': + p = reinterpret_cast<T*>(new S); + break; + case 'T': + p = new T; + break; + case 'U': + p = new U; + break; + case 'V': + p = reinterpret_cast<T*>(new U); + break; + } + + switch (argv[1][0]) { + case 'r': + // Binding a reference to storage of appropriate size and alignment is OK. + {T &r = *p;} + break; + + case 'm': + // CHECK-MEMBER: vptr.cpp:[[@LINE+5]]:15: runtime error: member access within address [[PTR:0x[0-9a-f]*]] which does not point to an object of type 'T' + // CHECK-MEMBER-NEXT: [[PTR]]: note: object is of type [[DYN_TYPE:'S'|'U']] + // CHECK-MEMBER-NEXT: {{^ .. .. .. .. .. .. .. .. .. .. .. .. }} + // CHECK-MEMBER-NEXT: {{^ \^~~~~~~~~~~(~~~~~~~~~~~~)? *$}} + // CHECK-MEMBER-NEXT: {{^ vptr for}} [[DYN_TYPE]] + return p->b; + + // CHECK-NULL-MEMBER: vptr.cpp:[[@LINE-2]]:15: runtime error: member access within address [[PTR:0x[0-9a-f]*]] which does not point to an object of type 'T' + // CHECK-NULL-MEMBER-NEXT: [[PTR]]: note: object has invalid vptr + // CHECK-NULL-MEMBER-NEXT: {{^ .. .. .. .. 00 00 00 00 00 00 00 00 }} + // CHECK-NULL-MEMBER-NEXT: {{^ \^~~~~~~~~~~(~~~~~~~~~~~~)? *$}} + // CHECK-NULL-MEMBER-NEXT: {{^ invalid vptr}} + + case 'f': + // CHECK-MEMFUN: vptr.cpp:[[@LINE+5]]:12: runtime error: member call on address [[PTR:0x[0-9a-f]*]] which does not point to an object of type 'T' + // CHECK-MEMFUN-NEXT: [[PTR]]: note: object is of type [[DYN_TYPE:'S'|'U']] + // CHECK-MEMFUN-NEXT: {{^ .. .. .. .. .. .. .. .. .. .. .. .. }} + // CHECK-MEMFUN-NEXT: {{^ \^~~~~~~~~~~(~~~~~~~~~~~~)? *$}} + // CHECK-MEMFUN-NEXT: {{^ vptr for}} [[DYN_TYPE]] + return p->g(); + + case 'o': + // CHECK-OFFSET: vptr.cpp:[[@LINE+5]]:12: runtime error: member call on address [[PTR:0x[0-9a-f]*]] which does not point to an object of type 'U' + // CHECK-OFFSET-NEXT: 0x{{[0-9a-f]*}}: note: object is base class subobject at offset {{8|16}} within object of type [[DYN_TYPE:'U']] + // CHECK-OFFSET-NEXT: {{^ .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. }} + // CHECK-OFFSET-NEXT: {{^ \^ ( ~~~~~~~~~~~~)~~~~~~~~~~~ *$}} + // CHECK-OFFSET-NEXT: {{^ ( )?vptr for}} 'T' base class of [[DYN_TYPE]] + return reinterpret_cast<U*>(p)->v() - 2; + } +} diff --git a/lib/ubsan/lit_tests/lit.cfg b/lib/ubsan/lit_tests/lit.cfg new file mode 100644 index 000000000000..9fd3a1aeaa16 --- /dev/null +++ b/lib/ubsan/lit_tests/lit.cfg @@ -0,0 +1,65 @@ +# -*- Python -*- + +import os + +# Setup config name. +config.name = 'UndefinedBehaviorSanitizer' + +# Setup source root. +config.test_source_root = os.path.dirname(__file__) + +def DisplayNoConfigMessage(): + lit.fatal("No site specific configuration available! " + + "Try running your test from the build tree or running " + + "make check-ubsan") + +# Figure out LLVM source root. +llvm_src_root = getattr(config, 'llvm_src_root', None) +if llvm_src_root is None: + # We probably haven't loaded the site-specific configuration: the user + # is likely trying to run a test file directly, and the site configuration + # wasn't created by the build system or we're performing an out-of-tree build. + ubsan_site_cfg = lit.params.get('ubsan_site_config', None) + if ubsan_site_cfg and os.path.exists(ubsan_site_cfg): + lit.load_config(config, ubsan_site_cfg) + raise SystemExit + + # Try to guess the location of site-specific configuration using llvm-config + # util that can point where the build tree is. + llvm_config = lit.util.which("llvm-config", config.environment["PATH"]) + if not llvm_config: + DisplayNoConfigMessage() + + # Validate that llvm-config points to the same source tree. + llvm_src_root = lit.util.capture(["llvm-config", "--src-root"]).strip() + ubsan_test_src_root = os.path.join(llvm_src_root, "projects", "compiler-rt", + "lib", "ubsan", "lit_tests") + if (os.path.realpath(ubsan_test_src_root) != + os.path.realpath(config.test_source_root)): + DisplayNoConfigMessage() + + # Find out the presumed location of generated site config. + llvm_obj_root = lit.util.capture(["llvm-config", "--obj-root"]).strip() + ubsan_site_cfg = os.path.join(llvm_obj_root, "projects", "compiler-rt", + "lib", "ubsan", "lit_tests", "lit.site.cfg") + if not ubsan_site_cfg or not os.path.exists(ubsan_site_cfg): + DisplayNoConfigMessage() + + lit.load_config(config, ubsan_site_cfg) + raise SystemExit + +# Setup attributes common for all compiler-rt projects. +compiler_rt_lit_cfg = os.path.join(llvm_src_root, "projects", "compiler-rt", + "lib", "lit.common.cfg") +if not compiler_rt_lit_cfg or not os.path.exists(compiler_rt_lit_cfg): + lit.fatal("Can't find common compiler-rt lit config at: %r" + % compiler_rt_lit_cfg) +lit.load_config(config, compiler_rt_lit_cfg) + +# Default test suffixes. +config.suffixes = ['.c', '.cc', '.cpp'] + +# UndefinedBehaviorSanitizer tests are currently supported on +# Linux and Darwin only. +if config.host_os not in ['Linux', 'Darwin']: + config.unsupported = True diff --git a/lib/ubsan/lit_tests/lit.site.cfg.in b/lib/ubsan/lit_tests/lit.site.cfg.in new file mode 100644 index 000000000000..b1c6ccf544ea --- /dev/null +++ b/lib/ubsan/lit_tests/lit.site.cfg.in @@ -0,0 +1,19 @@ +## Autogenerated by LLVM/Clang configuration. +# Do not edit! + +config.clang = "@LLVM_BINARY_DIR@/bin/clang" +config.host_os = "@HOST_OS@" +config.llvm_src_root = "@LLVM_SOURCE_DIR@" +config.llvm_tools_dir = "@LLVM_TOOLS_DIR@" +config.target_triple = "@TARGET_TRIPLE@" + +# LLVM tools dir can be passed in lit parameters, so try to +# apply substitution. +try: + config.llvm_tools_dir = config.llvm_tools_dir % lit.params +except KeyError,e: + key, = e.args + lit.fatal("unable to find %r parameter, use '--param=%s=VALUE'" % (key, key)) + +# Let the main config do the real work. +lit.load_config(config, "@CMAKE_CURRENT_SOURCE_DIR@/lit.cfg") diff --git a/lib/ubsan/ubsan_diag.cc b/lib/ubsan/ubsan_diag.cc new file mode 100644 index 000000000000..57c98e669e90 --- /dev/null +++ b/lib/ubsan/ubsan_diag.cc @@ -0,0 +1,271 @@ +//===-- ubsan_diag.cc -----------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Diagnostic reporting for the UBSan runtime. +// +//===----------------------------------------------------------------------===// + +#include "ubsan_diag.h" +#include "sanitizer_common/sanitizer_common.h" +#include "sanitizer_common/sanitizer_libc.h" +#include "sanitizer_common/sanitizer_stacktrace.h" +#include "sanitizer_common/sanitizer_symbolizer.h" +#include <stdio.h> + +using namespace __ubsan; + +Location __ubsan::getCallerLocation(uptr CallerLoc) { + if (!CallerLoc) + return Location(); + + uptr Loc = StackTrace::GetPreviousInstructionPc(CallerLoc); + + AddressInfo Info; + if (!SymbolizeCode(Loc, &Info, 1) || !Info.module || !*Info.module) + return Location(Loc); + + if (!Info.function) + return ModuleLocation(Info.module, Info.module_offset); + + return SourceLocation(Info.file, Info.line, Info.column); +} + +Diag &Diag::operator<<(const TypeDescriptor &V) { + return AddArg(V.getTypeName()); +} + +Diag &Diag::operator<<(const Value &V) { + if (V.getType().isSignedIntegerTy()) + AddArg(V.getSIntValue()); + else if (V.getType().isUnsignedIntegerTy()) + AddArg(V.getUIntValue()); + else if (V.getType().isFloatTy()) + AddArg(V.getFloatValue()); + else + AddArg("<unknown>"); + return *this; +} + +/// Hexadecimal printing for numbers too large for Printf to handle directly. +static void PrintHex(UIntMax Val) { +#if HAVE_INT128_T + Printf("0x%08x%08x%08x%08x", + (unsigned int)(Val >> 96), + (unsigned int)(Val >> 64), + (unsigned int)(Val >> 32), + (unsigned int)(Val)); +#else + UNREACHABLE("long long smaller than 64 bits?"); +#endif +} + +static void renderLocation(Location Loc) { + switch (Loc.getKind()) { + case Location::LK_Source: { + SourceLocation SLoc = Loc.getSourceLocation(); + if (SLoc.isInvalid()) + RawWrite("<unknown>:"); + else { + Printf("%s:%d:", SLoc.getFilename(), SLoc.getLine()); + if (SLoc.getColumn()) + Printf("%d:", SLoc.getColumn()); + } + break; + } + case Location::LK_Module: + Printf("%s:0x%zx:", Loc.getModuleLocation().getModuleName(), + Loc.getModuleLocation().getOffset()); + break; + case Location::LK_Memory: + Printf("%p:", Loc.getMemoryLocation()); + break; + case Location::LK_Null: + RawWrite("<unknown>:"); + break; + } +} + +static void renderText(const char *Message, const Diag::Arg *Args) { + for (const char *Msg = Message; *Msg; ++Msg) { + if (*Msg != '%') { + char Buffer[64]; + unsigned I; + for (I = 0; Msg[I] && Msg[I] != '%' && I != 63; ++I) + Buffer[I] = Msg[I]; + Buffer[I] = '\0'; + RawWrite(Buffer); + Msg += I - 1; + } else { + const Diag::Arg &A = Args[*++Msg - '0']; + switch (A.Kind) { + case Diag::AK_String: + Printf("%s", A.String); + break; + case Diag::AK_Mangled: { + RawWrite("'"); + RawWrite(Demangle(A.String)); + RawWrite("'"); + break; + } + case Diag::AK_SInt: + // 'long long' is guaranteed to be at least 64 bits wide. + if (A.SInt >= INT64_MIN && A.SInt <= INT64_MAX) + Printf("%lld", (long long)A.SInt); + else + PrintHex(A.SInt); + break; + case Diag::AK_UInt: + if (A.UInt <= UINT64_MAX) + Printf("%llu", (unsigned long long)A.UInt); + else + PrintHex(A.UInt); + break; + case Diag::AK_Float: { + // FIXME: Support floating-point formatting in sanitizer_common's + // printf, and stop using snprintf here. + char Buffer[32]; + snprintf(Buffer, sizeof(Buffer), "%Lg", (long double)A.Float); + Printf("%s", Buffer); + break; + } + case Diag::AK_Pointer: + Printf("%p", A.Pointer); + break; + } + } + } +} + +/// Find the earliest-starting range in Ranges which ends after Loc. +static Range *upperBound(MemoryLocation Loc, Range *Ranges, + unsigned NumRanges) { + Range *Best = 0; + for (unsigned I = 0; I != NumRanges; ++I) + if (Ranges[I].getEnd().getMemoryLocation() > Loc && + (!Best || + Best->getStart().getMemoryLocation() > + Ranges[I].getStart().getMemoryLocation())) + Best = &Ranges[I]; + return Best; +} + +/// Render a snippet of the address space near a location. +static void renderMemorySnippet(MemoryLocation Loc, + Range *Ranges, unsigned NumRanges, + const Diag::Arg *Args) { + const unsigned BytesToShow = 32; + const unsigned MinBytesNearLoc = 4; + + // Show at least the 8 bytes surrounding Loc. + MemoryLocation Min = Loc - MinBytesNearLoc, Max = Loc + MinBytesNearLoc; + for (unsigned I = 0; I < NumRanges; ++I) { + Min = __sanitizer::Min(Ranges[I].getStart().getMemoryLocation(), Min); + Max = __sanitizer::Max(Ranges[I].getEnd().getMemoryLocation(), Max); + } + + // If we have too many interesting bytes, prefer to show bytes after Loc. + if (Max - Min > BytesToShow) + Min = __sanitizer::Min(Max - BytesToShow, Loc - MinBytesNearLoc); + Max = Min + BytesToShow; + + // Emit data. + for (uptr P = Min; P != Max; ++P) { + // FIXME: Check that the address is readable before printing it. + unsigned char C = *reinterpret_cast<const unsigned char*>(P); + Printf("%s%02x", (P % 8 == 0) ? " " : " ", C); + } + RawWrite("\n"); + + // Emit highlights. + Range *InRange = upperBound(Min, Ranges, NumRanges); + for (uptr P = Min; P != Max; ++P) { + char Pad = ' ', Byte = ' '; + if (InRange && InRange->getEnd().getMemoryLocation() == P) + InRange = upperBound(P, Ranges, NumRanges); + if (!InRange && P > Loc) + break; + if (InRange && InRange->getStart().getMemoryLocation() < P) + Pad = '~'; + if (InRange && InRange->getStart().getMemoryLocation() <= P) + Byte = '~'; + char Buffer[] = { Pad, Pad, P == Loc ? '^' : Byte, Byte, 0 }; + RawWrite((P % 8 == 0) ? Buffer : &Buffer[1]); + } + RawWrite("\n"); + + // Go over the line again, and print names for the ranges. + InRange = 0; + unsigned Spaces = 0; + for (uptr P = Min; P != Max; ++P) { + if (!InRange || InRange->getEnd().getMemoryLocation() == P) + InRange = upperBound(P, Ranges, NumRanges); + if (!InRange) + break; + + Spaces += (P % 8) == 0 ? 2 : 1; + + if (InRange && InRange->getStart().getMemoryLocation() == P) { + while (Spaces--) + RawWrite(" "); + renderText(InRange->getText(), Args); + RawWrite("\n"); + // FIXME: We only support naming one range for now! + break; + } + + Spaces += 2; + } + + // FIXME: Print names for anything we can identify within the line: + // + // * If we can identify the memory itself as belonging to a particular + // global, stack variable, or dynamic allocation, then do so. + // + // * If we have a pointer-size, pointer-aligned range highlighted, + // determine whether the value of that range is a pointer to an + // entity which we can name, and if so, print that name. + // + // This needs an external symbolizer, or (preferably) ASan instrumentation. +} + +Diag::~Diag() { + bool UseAnsiColor = PrintsToTty(); + if (UseAnsiColor) + RawWrite("\033[1m"); + + renderLocation(Loc); + + switch (Level) { + case DL_Error: + if (UseAnsiColor) + RawWrite("\033[31m"); + RawWrite(" runtime error: "); + if (UseAnsiColor) + RawWrite("\033[0;1m"); + break; + + case DL_Note: + if (UseAnsiColor) + RawWrite("\033[30m"); + RawWrite(" note: "); + if (UseAnsiColor) + RawWrite("\033[0m"); + break; + } + + renderText(Message, Args); + + if (UseAnsiColor) + RawWrite("\033[0m"); + + RawWrite("\n"); + + if (Loc.isMemoryLocation()) + renderMemorySnippet(Loc.getMemoryLocation(), Ranges, NumRanges, Args); +} diff --git a/lib/ubsan/ubsan_diag.h b/lib/ubsan/ubsan_diag.h new file mode 100644 index 000000000000..16afffdb0a76 --- /dev/null +++ b/lib/ubsan/ubsan_diag.h @@ -0,0 +1,202 @@ +//===-- ubsan_diag.h --------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Diagnostics emission for Clang's undefined behavior sanitizer. +// +//===----------------------------------------------------------------------===// +#ifndef UBSAN_DIAG_H +#define UBSAN_DIAG_H + +#include "ubsan_value.h" + +namespace __ubsan { + +/// \brief A location within a loaded module in the program. These are used when +/// the location can't be resolved to a SourceLocation. +class ModuleLocation { + const char *ModuleName; + uptr Offset; + +public: + ModuleLocation() : ModuleName(0), Offset(0) {} + ModuleLocation(const char *ModuleName, uptr Offset) + : ModuleName(ModuleName), Offset(Offset) {} + const char *getModuleName() const { return ModuleName; } + uptr getOffset() const { return Offset; } +}; + +/// A location of some data within the program's address space. +typedef uptr MemoryLocation; + +/// \brief Location at which a diagnostic can be emitted. Either a +/// SourceLocation, a ModuleLocation, or a MemoryLocation. +class Location { +public: + enum LocationKind { LK_Null, LK_Source, LK_Module, LK_Memory }; + +private: + LocationKind Kind; + // FIXME: In C++11, wrap these in an anonymous union. + SourceLocation SourceLoc; + ModuleLocation ModuleLoc; + MemoryLocation MemoryLoc; + +public: + Location() : Kind(LK_Null) {} + Location(SourceLocation Loc) : + Kind(LK_Source), SourceLoc(Loc) {} + Location(ModuleLocation Loc) : + Kind(LK_Module), ModuleLoc(Loc) {} + Location(MemoryLocation Loc) : + Kind(LK_Memory), MemoryLoc(Loc) {} + + LocationKind getKind() const { return Kind; } + + bool isSourceLocation() const { return Kind == LK_Source; } + bool isModuleLocation() const { return Kind == LK_Module; } + bool isMemoryLocation() const { return Kind == LK_Memory; } + + SourceLocation getSourceLocation() const { + CHECK(isSourceLocation()); + return SourceLoc; + } + ModuleLocation getModuleLocation() const { + CHECK(isModuleLocation()); + return ModuleLoc; + } + MemoryLocation getMemoryLocation() const { + CHECK(isMemoryLocation()); + return MemoryLoc; + } +}; + +/// Try to obtain a location for the caller. This might fail, and produce either +/// an invalid location or a module location for the caller. +Location getCallerLocation(uptr CallerLoc = GET_CALLER_PC()); + +/// A diagnostic severity level. +enum DiagLevel { + DL_Error, ///< An error. + DL_Note ///< A note, attached to a prior diagnostic. +}; + +/// \brief Annotation for a range of locations in a diagnostic. +class Range { + Location Start, End; + const char *Text; + +public: + Range() : Start(), End(), Text() {} + Range(MemoryLocation Start, MemoryLocation End, const char *Text) + : Start(Start), End(End), Text(Text) {} + Location getStart() const { return Start; } + Location getEnd() const { return End; } + const char *getText() const { return Text; } +}; + +/// \brief A mangled C++ name. Really just a strong typedef for 'const char*'. +class MangledName { + const char *Name; +public: + MangledName(const char *Name) : Name(Name) {} + const char *getName() const { return Name; } +}; + +/// \brief Representation of an in-flight diagnostic. +/// +/// Temporary \c Diag instances are created by the handler routines to +/// accumulate arguments for a diagnostic. The destructor emits the diagnostic +/// message. +class Diag { + /// The location at which the problem occurred. + Location Loc; + + /// The diagnostic level. + DiagLevel Level; + + /// The message which will be emitted, with %0, %1, ... placeholders for + /// arguments. + const char *Message; + +public: + /// Kinds of arguments, corresponding to members of \c Arg's union. + enum ArgKind { + AK_String, ///< A string argument, displayed as-is. + AK_Mangled,///< A C++ mangled name, demangled before display. + AK_UInt, ///< An unsigned integer argument. + AK_SInt, ///< A signed integer argument. + AK_Float, ///< A floating-point argument. + AK_Pointer ///< A pointer argument, displayed in hexadecimal. + }; + + /// An individual diagnostic message argument. + struct Arg { + Arg() {} + Arg(const char *String) : Kind(AK_String), String(String) {} + Arg(MangledName MN) : Kind(AK_Mangled), String(MN.getName()) {} + Arg(UIntMax UInt) : Kind(AK_UInt), UInt(UInt) {} + Arg(SIntMax SInt) : Kind(AK_SInt), SInt(SInt) {} + Arg(FloatMax Float) : Kind(AK_Float), Float(Float) {} + Arg(const void *Pointer) : Kind(AK_Pointer), Pointer(Pointer) {} + + ArgKind Kind; + union { + const char *String; + UIntMax UInt; + SIntMax SInt; + FloatMax Float; + const void *Pointer; + }; + }; + +private: + static const unsigned MaxArgs = 5; + static const unsigned MaxRanges = 1; + + /// The arguments which have been added to this diagnostic so far. + Arg Args[MaxArgs]; + unsigned NumArgs; + + /// The ranges which have been added to this diagnostic so far. + Range Ranges[MaxRanges]; + unsigned NumRanges; + + Diag &AddArg(Arg A) { + CHECK(NumArgs != MaxArgs); + Args[NumArgs++] = A; + return *this; + } + + Diag &AddRange(Range A) { + CHECK(NumRanges != MaxRanges); + Ranges[NumRanges++] = A; + return *this; + } + + /// \c Diag objects are not copyable. + Diag(const Diag &); // NOT IMPLEMENTED + Diag &operator=(const Diag &); + +public: + Diag(Location Loc, DiagLevel Level, const char *Message) + : Loc(Loc), Level(Level), Message(Message), NumArgs(0), NumRanges(0) {} + ~Diag(); + + Diag &operator<<(const char *Str) { return AddArg(Str); } + Diag &operator<<(MangledName MN) { return AddArg(MN); } + Diag &operator<<(unsigned long long V) { return AddArg(UIntMax(V)); } + Diag &operator<<(const void *V) { return AddArg(V); } + Diag &operator<<(const TypeDescriptor &V); + Diag &operator<<(const Value &V); + Diag &operator<<(const Range &R) { return AddRange(R); } +}; + +} // namespace __ubsan + +#endif // UBSAN_DIAG_H diff --git a/lib/ubsan/ubsan_handlers.cc b/lib/ubsan/ubsan_handlers.cc new file mode 100644 index 000000000000..1b02aa0fadf3 --- /dev/null +++ b/lib/ubsan/ubsan_handlers.cc @@ -0,0 +1,244 @@ +//===-- ubsan_handlers.cc -------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Error logging entry points for the UBSan runtime. +// +//===----------------------------------------------------------------------===// + +#include "ubsan_handlers.h" +#include "ubsan_diag.h" + +#include "sanitizer_common/sanitizer_common.h" + +using namespace __sanitizer; +using namespace __ubsan; + +namespace __ubsan { + const char *TypeCheckKinds[] = { + "load of", "store to", "reference binding to", "member access within", + "member call on", "constructor call on" + }; +} + +static void handleTypeMismatchImpl(TypeMismatchData *Data, ValueHandle Pointer, + Location FallbackLoc) { + Location Loc = Data->Loc.acquire(); + + // Use the SourceLocation from Data to track deduplication, even if 'invalid' + if (Loc.getSourceLocation().isDisabled()) + return; + if (Data->Loc.isInvalid()) + Loc = FallbackLoc; + + if (!Pointer) + Diag(Loc, DL_Error, "%0 null pointer of type %1") + << TypeCheckKinds[Data->TypeCheckKind] << Data->Type; + else if (Data->Alignment && (Pointer & (Data->Alignment - 1))) + Diag(Loc, DL_Error, "%0 misaligned address %1 for type %3, " + "which requires %2 byte alignment") + << TypeCheckKinds[Data->TypeCheckKind] << (void*)Pointer + << Data->Alignment << Data->Type; + else + Diag(Loc, DL_Error, "%0 address %1 with insufficient space " + "for an object of type %2") + << TypeCheckKinds[Data->TypeCheckKind] << (void*)Pointer << Data->Type; + if (Pointer) + Diag(Pointer, DL_Note, "pointer points here"); +} +void __ubsan::__ubsan_handle_type_mismatch(TypeMismatchData *Data, + ValueHandle Pointer) { + handleTypeMismatchImpl(Data, Pointer, getCallerLocation()); +} +void __ubsan::__ubsan_handle_type_mismatch_abort(TypeMismatchData *Data, + ValueHandle Pointer) { + handleTypeMismatchImpl(Data, Pointer, getCallerLocation()); + Die(); +} + +/// \brief Common diagnostic emission for various forms of integer overflow. +template<typename T> static void HandleIntegerOverflow(OverflowData *Data, + ValueHandle LHS, + const char *Operator, + T RHS) { + SourceLocation Loc = Data->Loc.acquire(); + if (Loc.isDisabled()) + return; + + Diag(Loc, DL_Error, "%0 integer overflow: " + "%1 %2 %3 cannot be represented in type %4") + << (Data->Type.isSignedIntegerTy() ? "signed" : "unsigned") + << Value(Data->Type, LHS) << Operator << RHS << Data->Type; +} + +void __ubsan::__ubsan_handle_add_overflow(OverflowData *Data, + ValueHandle LHS, ValueHandle RHS) { + HandleIntegerOverflow(Data, LHS, "+", Value(Data->Type, RHS)); +} +void __ubsan::__ubsan_handle_add_overflow_abort(OverflowData *Data, + ValueHandle LHS, + ValueHandle RHS) { + __ubsan_handle_add_overflow(Data, LHS, RHS); + Die(); +} + +void __ubsan::__ubsan_handle_sub_overflow(OverflowData *Data, + ValueHandle LHS, ValueHandle RHS) { + HandleIntegerOverflow(Data, LHS, "-", Value(Data->Type, RHS)); +} +void __ubsan::__ubsan_handle_sub_overflow_abort(OverflowData *Data, + ValueHandle LHS, + ValueHandle RHS) { + __ubsan_handle_sub_overflow(Data, LHS, RHS); + Die(); +} + +void __ubsan::__ubsan_handle_mul_overflow(OverflowData *Data, + ValueHandle LHS, ValueHandle RHS) { + HandleIntegerOverflow(Data, LHS, "*", Value(Data->Type, RHS)); +} +void __ubsan::__ubsan_handle_mul_overflow_abort(OverflowData *Data, + ValueHandle LHS, + ValueHandle RHS) { + __ubsan_handle_mul_overflow(Data, LHS, RHS); + Die(); +} + +void __ubsan::__ubsan_handle_negate_overflow(OverflowData *Data, + ValueHandle OldVal) { + SourceLocation Loc = Data->Loc.acquire(); + if (Loc.isDisabled()) + return; + + if (Data->Type.isSignedIntegerTy()) + Diag(Loc, DL_Error, + "negation of %0 cannot be represented in type %1; " + "cast to an unsigned type to negate this value to itself") + << Value(Data->Type, OldVal) << Data->Type; + else + Diag(Loc, DL_Error, + "negation of %0 cannot be represented in type %1") + << Value(Data->Type, OldVal) << Data->Type; +} +void __ubsan::__ubsan_handle_negate_overflow_abort(OverflowData *Data, + ValueHandle OldVal) { + __ubsan_handle_negate_overflow(Data, OldVal); + Die(); +} + +void __ubsan::__ubsan_handle_divrem_overflow(OverflowData *Data, + ValueHandle LHS, ValueHandle RHS) { + SourceLocation Loc = Data->Loc.acquire(); + if (Loc.isDisabled()) + return; + + Value LHSVal(Data->Type, LHS); + Value RHSVal(Data->Type, RHS); + if (RHSVal.isMinusOne()) + Diag(Loc, DL_Error, + "division of %0 by -1 cannot be represented in type %1") + << LHSVal << Data->Type; + else + Diag(Loc, DL_Error, "division by zero"); +} +void __ubsan::__ubsan_handle_divrem_overflow_abort(OverflowData *Data, + ValueHandle LHS, + ValueHandle RHS) { + __ubsan_handle_divrem_overflow(Data, LHS, RHS); + Die(); +} + +void __ubsan::__ubsan_handle_shift_out_of_bounds(ShiftOutOfBoundsData *Data, + ValueHandle LHS, + ValueHandle RHS) { + SourceLocation Loc = Data->Loc.acquire(); + if (Loc.isDisabled()) + return; + + Value LHSVal(Data->LHSType, LHS); + Value RHSVal(Data->RHSType, RHS); + if (RHSVal.isNegative()) + Diag(Loc, DL_Error, "shift exponent %0 is negative") << RHSVal; + else if (RHSVal.getPositiveIntValue() >= Data->LHSType.getIntegerBitWidth()) + Diag(Loc, DL_Error, + "shift exponent %0 is too large for %1-bit type %2") + << RHSVal << Data->LHSType.getIntegerBitWidth() << Data->LHSType; + else if (LHSVal.isNegative()) + Diag(Loc, DL_Error, "left shift of negative value %0") << LHSVal; + else + Diag(Loc, DL_Error, + "left shift of %0 by %1 places cannot be represented in type %2") + << LHSVal << RHSVal << Data->LHSType; +} +void __ubsan::__ubsan_handle_shift_out_of_bounds_abort( + ShiftOutOfBoundsData *Data, + ValueHandle LHS, + ValueHandle RHS) { + __ubsan_handle_shift_out_of_bounds(Data, LHS, RHS); + Die(); +} + +void __ubsan::__ubsan_handle_builtin_unreachable(UnreachableData *Data) { + Diag(Data->Loc, DL_Error, "execution reached a __builtin_unreachable() call"); + Die(); +} + +void __ubsan::__ubsan_handle_missing_return(UnreachableData *Data) { + Diag(Data->Loc, DL_Error, + "execution reached the end of a value-returning function " + "without returning a value"); + Die(); +} + +void __ubsan::__ubsan_handle_vla_bound_not_positive(VLABoundData *Data, + ValueHandle Bound) { + SourceLocation Loc = Data->Loc.acquire(); + if (Loc.isDisabled()) + return; + + Diag(Loc, DL_Error, "variable length array bound evaluates to " + "non-positive value %0") + << Value(Data->Type, Bound); +} +void __ubsan::__ubsan_handle_vla_bound_not_positive_abort(VLABoundData *Data, + ValueHandle Bound) { + __ubsan_handle_vla_bound_not_positive(Data, Bound); + Die(); +} + + +void __ubsan::__ubsan_handle_float_cast_overflow(FloatCastOverflowData *Data, + ValueHandle From) { + // TODO: Add deduplication once a SourceLocation is generated for this check. + Diag(getCallerLocation(), DL_Error, + "value %0 is outside the range of representable values of type %2") + << Value(Data->FromType, From) << Data->FromType << Data->ToType; +} +void __ubsan::__ubsan_handle_float_cast_overflow_abort( + FloatCastOverflowData *Data, + ValueHandle From) { + Diag(getCallerLocation(), DL_Error, + "value %0 is outside the range of representable values of type %2") + << Value(Data->FromType, From) << Data->FromType << Data->ToType; + Die(); +} + +void __ubsan::__ubsan_handle_load_invalid_value(InvalidValueData *Data, + ValueHandle Val) { + // TODO: Add deduplication once a SourceLocation is generated for this check. + Diag(getCallerLocation(), DL_Error, + "load of value %0, which is not a valid value for type %1") + << Value(Data->Type, Val) << Data->Type; +} +void __ubsan::__ubsan_handle_load_invalid_value_abort(InvalidValueData *Data, + ValueHandle Val) { + Diag(getCallerLocation(), DL_Error, + "load of value %0, which is not a valid value for type %1") + << Value(Data->Type, Val) << Data->Type; + Die(); +} diff --git a/lib/ubsan/ubsan_handlers.h b/lib/ubsan/ubsan_handlers.h new file mode 100644 index 000000000000..d6a042481ffa --- /dev/null +++ b/lib/ubsan/ubsan_handlers.h @@ -0,0 +1,108 @@ +//===-- ubsan_handlers.h ----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Entry points to the runtime library for Clang's undefined behavior sanitizer. +// +//===----------------------------------------------------------------------===// +#ifndef UBSAN_HANDLERS_H +#define UBSAN_HANDLERS_H + +#include "ubsan_value.h" + +namespace __ubsan { + +struct TypeMismatchData { + SourceLocation Loc; + const TypeDescriptor &Type; + uptr Alignment; + unsigned char TypeCheckKind; +}; + +#define RECOVERABLE(checkname, ...) \ + extern "C" SANITIZER_INTERFACE_ATTRIBUTE \ + void __ubsan_handle_ ## checkname( __VA_ARGS__ ); \ + extern "C" SANITIZER_INTERFACE_ATTRIBUTE \ + void __ubsan_handle_ ## checkname ## _abort( __VA_ARGS__ ); + +/// \brief Handle a runtime type check failure, caused by either a misaligned +/// pointer, a null pointer, or a pointer to insufficient storage for the +/// type. +RECOVERABLE(type_mismatch, TypeMismatchData *Data, ValueHandle Pointer) + +struct OverflowData { + SourceLocation Loc; + const TypeDescriptor &Type; +}; + +/// \brief Handle an integer addition overflow. +RECOVERABLE(add_overflow, OverflowData *Data, ValueHandle LHS, ValueHandle RHS) + +/// \brief Handle an integer subtraction overflow. +RECOVERABLE(sub_overflow, OverflowData *Data, ValueHandle LHS, ValueHandle RHS) + +/// \brief Handle an integer multiplication overflow. +RECOVERABLE(mul_overflow, OverflowData *Data, ValueHandle LHS, ValueHandle RHS) + +/// \brief Handle a signed integer overflow for a unary negate operator. +RECOVERABLE(negate_overflow, OverflowData *Data, ValueHandle OldVal) + +/// \brief Handle an INT_MIN/-1 overflow or division by zero. +RECOVERABLE(divrem_overflow, OverflowData *Data, + ValueHandle LHS, ValueHandle RHS) + +struct ShiftOutOfBoundsData { + SourceLocation Loc; + const TypeDescriptor &LHSType; + const TypeDescriptor &RHSType; +}; + +/// \brief Handle a shift where the RHS is out of bounds or a left shift where +/// the LHS is negative or overflows. +RECOVERABLE(shift_out_of_bounds, ShiftOutOfBoundsData *Data, + ValueHandle LHS, ValueHandle RHS) + +struct UnreachableData { + SourceLocation Loc; +}; + +/// \brief Handle a __builtin_unreachable which is reached. +extern "C" SANITIZER_INTERFACE_ATTRIBUTE +void __ubsan_handle_builtin_unreachable(UnreachableData *Data); +/// \brief Handle reaching the end of a value-returning function. +extern "C" SANITIZER_INTERFACE_ATTRIBUTE +void __ubsan_handle_missing_return(UnreachableData *Data); + +struct VLABoundData { + SourceLocation Loc; + const TypeDescriptor &Type; +}; + +/// \brief Handle a VLA with a non-positive bound. +RECOVERABLE(vla_bound_not_positive, VLABoundData *Data, ValueHandle Bound) + +struct FloatCastOverflowData { + // FIXME: SourceLocation Loc; + const TypeDescriptor &FromType; + const TypeDescriptor &ToType; +}; + +/// \brief Handle overflow in a conversion to or from a floating-point type. +RECOVERABLE(float_cast_overflow, FloatCastOverflowData *Data, ValueHandle From) + +struct InvalidValueData { + // FIXME: SourceLocation Loc; + const TypeDescriptor &Type; +}; + +/// \brief Handle a load of an invalid value for the type. +RECOVERABLE(load_invalid_value, InvalidValueData *Data, ValueHandle Val) + +} + +#endif // UBSAN_HANDLERS_H diff --git a/lib/ubsan/ubsan_handlers_cxx.cc b/lib/ubsan/ubsan_handlers_cxx.cc new file mode 100644 index 000000000000..dcc1f60078d2 --- /dev/null +++ b/lib/ubsan/ubsan_handlers_cxx.cc @@ -0,0 +1,75 @@ +//===-- ubsan_handlers_cxx.cc ---------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Error logging entry points for the UBSan runtime, which are only used for C++ +// compilations. This file is permitted to use language features which require +// linking against a C++ ABI library. +// +//===----------------------------------------------------------------------===// + +#include "ubsan_handlers_cxx.h" +#include "ubsan_diag.h" +#include "ubsan_type_hash.h" + +#include "sanitizer_common/sanitizer_common.h" + +using namespace __sanitizer; +using namespace __ubsan; + +namespace __ubsan { + extern const char *TypeCheckKinds[]; +} + +static void HandleDynamicTypeCacheMiss( + DynamicTypeCacheMissData *Data, ValueHandle Pointer, ValueHandle Hash, + bool Abort) { + if (checkDynamicType((void*)Pointer, Data->TypeInfo, Hash)) + // Just a cache miss. The type matches after all. + return; + + SourceLocation Loc = Data->Loc.acquire(); + if (Loc.isDisabled()) + return; + + Diag(Loc, DL_Error, + "%0 address %1 which does not point to an object of type %2") + << TypeCheckKinds[Data->TypeCheckKind] << (void*)Pointer << Data->Type; + + // If possible, say what type it actually points to. + // FIXME: Demangle the type names. + DynamicTypeInfo DTI = getDynamicTypeInfo((void*)Pointer); + if (!DTI.isValid()) + Diag(Pointer, DL_Note, "object has invalid vptr") + << MangledName(DTI.getMostDerivedTypeName()) + << Range(Pointer, Pointer + sizeof(uptr), "invalid vptr"); + else if (!DTI.getOffset()) + Diag(Pointer, DL_Note, "object is of type %0") + << MangledName(DTI.getMostDerivedTypeName()) + << Range(Pointer, Pointer + sizeof(uptr), "vptr for %0"); + else + // FIXME: Find the type at the specified offset, and include that + // in the note. + Diag(Pointer - DTI.getOffset(), DL_Note, + "object is base class subobject at offset %0 within object of type %1") + << DTI.getOffset() << MangledName(DTI.getMostDerivedTypeName()) + << MangledName(DTI.getSubobjectTypeName()) + << Range(Pointer, Pointer + sizeof(uptr), "vptr for %2 base class of %1"); + + if (Abort) + Die(); +} + +void __ubsan::__ubsan_handle_dynamic_type_cache_miss( + DynamicTypeCacheMissData *Data, ValueHandle Pointer, ValueHandle Hash) { + HandleDynamicTypeCacheMiss(Data, Pointer, Hash, false); +} +void __ubsan::__ubsan_handle_dynamic_type_cache_miss_abort( + DynamicTypeCacheMissData *Data, ValueHandle Pointer, ValueHandle Hash) { + HandleDynamicTypeCacheMiss(Data, Pointer, Hash, true); +} diff --git a/lib/ubsan/ubsan_handlers_cxx.h b/lib/ubsan/ubsan_handlers_cxx.h new file mode 100644 index 000000000000..cb1bca78b833 --- /dev/null +++ b/lib/ubsan/ubsan_handlers_cxx.h @@ -0,0 +1,40 @@ +//===-- ubsan_handlers_cxx.h ------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Entry points to the runtime library for Clang's undefined behavior sanitizer, +// for C++-specific checks. This code is not linked into C binaries. +// +//===----------------------------------------------------------------------===// +#ifndef UBSAN_HANDLERS_CXX_H +#define UBSAN_HANDLERS_CXX_H + +#include "ubsan_value.h" + +namespace __ubsan { + +struct DynamicTypeCacheMissData { + SourceLocation Loc; + const TypeDescriptor &Type; + void *TypeInfo; + unsigned char TypeCheckKind; +}; + +/// \brief Handle a runtime type check failure, caused by an incorrect vptr. +/// When this handler is called, all we know is that the type was not in the +/// cache; this does not necessarily imply the existence of a bug. +extern "C" SANITIZER_INTERFACE_ATTRIBUTE +void __ubsan_handle_dynamic_type_cache_miss( + DynamicTypeCacheMissData *Data, ValueHandle Pointer, ValueHandle Hash); +extern "C" SANITIZER_INTERFACE_ATTRIBUTE +void __ubsan_handle_dynamic_type_cache_miss_abort( + DynamicTypeCacheMissData *Data, ValueHandle Pointer, ValueHandle Hash); + +} + +#endif // UBSAN_HANDLERS_H diff --git a/lib/ubsan/ubsan_type_hash.cc b/lib/ubsan/ubsan_type_hash.cc new file mode 100644 index 000000000000..7a9cd28f6ec0 --- /dev/null +++ b/lib/ubsan/ubsan_type_hash.cc @@ -0,0 +1,248 @@ +//===-- ubsan_type_hash.cc ------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Implementation of a hash table for fast checking of inheritance +// relationships. This file is only linked into C++ compilations, and is +// permitted to use language features which require a C++ ABI library. +// +//===----------------------------------------------------------------------===// + +#include "ubsan_type_hash.h" + +#include "sanitizer_common/sanitizer_common.h" + +// The following are intended to be binary compatible with the definitions +// given in the Itanium ABI. We make no attempt to be ODR-compatible with +// those definitions, since existing ABI implementations aren't. + +namespace std { + class type_info { + public: + virtual ~type_info(); + + const char *__type_name; + }; +} + +namespace __cxxabiv1 { + +/// Type info for classes with no bases, and base class for type info for +/// classes with bases. +class __class_type_info : public std::type_info { + virtual ~__class_type_info(); +}; + +/// Type info for classes with simple single public inheritance. +class __si_class_type_info : public __class_type_info { +public: + virtual ~__si_class_type_info(); + + const __class_type_info *__base_type; +}; + +class __base_class_type_info { +public: + const __class_type_info *__base_type; + long __offset_flags; + + enum __offset_flags_masks { + __virtual_mask = 0x1, + __public_mask = 0x2, + __offset_shift = 8 + }; +}; + +/// Type info for classes with multiple, virtual, or non-public inheritance. +class __vmi_class_type_info : public __class_type_info { +public: + virtual ~__vmi_class_type_info(); + + unsigned int flags; + unsigned int base_count; + __base_class_type_info base_info[1]; +}; + +} + +namespace abi = __cxxabiv1; + +// We implement a simple two-level cache for type-checking results. For each +// (vptr,type) pair, a hash is computed. This hash is assumed to be globally +// unique; if it collides, we will get false negatives, but: +// * such a collision would have to occur on the *first* bad access, +// * the probability of such a collision is low (and for a 64-bit target, is +// negligible), and +// * the vptr, and thus the hash, can be affected by ASLR, so multiple runs +// give better coverage. +// +// The first caching layer is a small hash table with no chaining; buckets are +// reused as needed. The second caching layer is a large hash table with open +// chaining. We can freely evict from either layer since this is just a cache. +// +// FIXME: Make these hash table accesses thread-safe. The races here are benign +// (worst-case, we could miss a bug or see a slowdown) but we should +// avoid upsetting race detectors. + +/// Find a bucket to store the given hash value in. +static __ubsan::HashValue *getTypeCacheHashTableBucket(__ubsan::HashValue V) { + static const unsigned HashTableSize = 65537; + static __ubsan::HashValue __ubsan_vptr_hash_set[HashTableSize] = { 1 }; + + unsigned Probe = V & 65535; + for (int Tries = 5; Tries; --Tries) { + if (!__ubsan_vptr_hash_set[Probe] || __ubsan_vptr_hash_set[Probe] == V) + return &__ubsan_vptr_hash_set[Probe]; + Probe += ((V >> 16) & 65535) + 1; + if (Probe >= HashTableSize) + Probe -= HashTableSize; + } + // FIXME: Pick a random entry from the probe sequence to evict rather than + // just taking the first. + return &__ubsan_vptr_hash_set[V]; +} + +/// A cache of recently-checked hashes. Mini hash table with "random" evictions. +__ubsan::HashValue +__ubsan::__ubsan_vptr_type_cache[__ubsan::VptrTypeCacheSize] = { 1 }; + +/// \brief Determine whether \p Derived has a \p Base base class subobject at +/// offset \p Offset. +static bool isDerivedFromAtOffset(const abi::__class_type_info *Derived, + const abi::__class_type_info *Base, + sptr Offset) { + if (Derived == Base) + return Offset == 0; + + if (const abi::__si_class_type_info *SI = + dynamic_cast<const abi::__si_class_type_info*>(Derived)) + return isDerivedFromAtOffset(SI->__base_type, Base, Offset); + + const abi::__vmi_class_type_info *VTI = + dynamic_cast<const abi::__vmi_class_type_info*>(Derived); + if (!VTI) + // No base class subobjects. + return false; + + // Look for a base class which is derived from \p Base at the right offset. + for (unsigned int base = 0; base != VTI->base_count; ++base) { + // FIXME: Curtail the recursion if this base can't possibly contain the + // given offset. + sptr OffsetHere = VTI->base_info[base].__offset_flags >> + abi::__base_class_type_info::__offset_shift; + if (VTI->base_info[base].__offset_flags & + abi::__base_class_type_info::__virtual_mask) + // For now, just punt on virtual bases and say 'yes'. + // FIXME: OffsetHere is the offset in the vtable of the virtual base + // offset. Read the vbase offset out of the vtable and use it. + return true; + if (isDerivedFromAtOffset(VTI->base_info[base].__base_type, + Base, Offset - OffsetHere)) + return true; + } + + return false; +} + +/// \brief Find the derived-most dynamic base class of \p Derived at offset +/// \p Offset. +static const abi::__class_type_info *findBaseAtOffset( + const abi::__class_type_info *Derived, sptr Offset) { + if (!Offset) + return Derived; + + if (const abi::__si_class_type_info *SI = + dynamic_cast<const abi::__si_class_type_info*>(Derived)) + return findBaseAtOffset(SI->__base_type, Offset); + + const abi::__vmi_class_type_info *VTI = + dynamic_cast<const abi::__vmi_class_type_info*>(Derived); + if (!VTI) + // No base class subobjects. + return 0; + + for (unsigned int base = 0; base != VTI->base_count; ++base) { + sptr OffsetHere = VTI->base_info[base].__offset_flags >> + abi::__base_class_type_info::__offset_shift; + if (VTI->base_info[base].__offset_flags & + abi::__base_class_type_info::__virtual_mask) + // FIXME: Can't handle virtual bases yet. + continue; + if (const abi::__class_type_info *Base = + findBaseAtOffset(VTI->base_info[base].__base_type, + Offset - OffsetHere)) + return Base; + } + + return 0; +} + +namespace { + +struct VtablePrefix { + /// The offset from the vptr to the start of the most-derived object. + /// This should never be greater than zero, and will usually be exactly + /// zero. + sptr Offset; + /// The type_info object describing the most-derived class type. + std::type_info *TypeInfo; +}; +VtablePrefix *getVtablePrefix(void *Object) { + VtablePrefix **VptrPtr = reinterpret_cast<VtablePrefix**>(Object); + if (!*VptrPtr) + return 0; + VtablePrefix *Prefix = *VptrPtr - 1; + if (Prefix->Offset > 0 || !Prefix->TypeInfo) + // This can't possibly be a valid vtable. + return 0; + return Prefix; +} + +} + +bool __ubsan::checkDynamicType(void *Object, void *Type, HashValue Hash) { + // A crash anywhere within this function probably means the vptr is corrupted. + // FIXME: Perform these checks more cautiously. + + // Check whether this is something we've evicted from the cache. + HashValue *Bucket = getTypeCacheHashTableBucket(Hash); + if (*Bucket == Hash) { + __ubsan_vptr_type_cache[Hash % VptrTypeCacheSize] = Hash; + return true; + } + + VtablePrefix *Vtable = getVtablePrefix(Object); + if (!Vtable) + return false; + + // Check that this is actually a type_info object for a class type. + abi::__class_type_info *Derived = + dynamic_cast<abi::__class_type_info*>(Vtable->TypeInfo); + if (!Derived) + return false; + + abi::__class_type_info *Base = (abi::__class_type_info*)Type; + if (!isDerivedFromAtOffset(Derived, Base, -Vtable->Offset)) + return false; + + // Success. Cache this result. + __ubsan_vptr_type_cache[Hash % VptrTypeCacheSize] = Hash; + *Bucket = Hash; + return true; +} + +__ubsan::DynamicTypeInfo __ubsan::getDynamicTypeInfo(void *Object) { + VtablePrefix *Vtable = getVtablePrefix(Object); + if (!Vtable) + return DynamicTypeInfo(0, 0, 0); + const abi::__class_type_info *ObjectType = findBaseAtOffset( + static_cast<const abi::__class_type_info*>(Vtable->TypeInfo), + -Vtable->Offset); + return DynamicTypeInfo(Vtable->TypeInfo->__type_name, -Vtable->Offset, + ObjectType ? ObjectType->__type_name : "<unknown>"); +} diff --git a/lib/ubsan/ubsan_type_hash.h b/lib/ubsan/ubsan_type_hash.h new file mode 100644 index 000000000000..58ecd3de9864 --- /dev/null +++ b/lib/ubsan/ubsan_type_hash.h @@ -0,0 +1,63 @@ +//===-- ubsan_type_hash.h ---------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Hashing of types for Clang's undefined behavior checker. +// +//===----------------------------------------------------------------------===// +#ifndef UBSAN_TYPE_HASH_H +#define UBSAN_TYPE_HASH_H + +#include "sanitizer_common/sanitizer_common.h" + +namespace __ubsan { + +typedef uptr HashValue; + +/// \brief Information about the dynamic type of an object (extracted from its +/// vptr). +class DynamicTypeInfo { + const char *MostDerivedTypeName; + sptr Offset; + const char *SubobjectTypeName; + +public: + DynamicTypeInfo(const char *MDTN, sptr Offset, const char *STN) + : MostDerivedTypeName(MDTN), Offset(Offset), SubobjectTypeName(STN) {} + + /// Determine whether the object had a valid dynamic type. + bool isValid() const { return MostDerivedTypeName; } + /// Get the name of the most-derived type of the object. + const char *getMostDerivedTypeName() const { return MostDerivedTypeName; } + /// Get the offset from the most-derived type to this base class. + sptr getOffset() const { return Offset; } + /// Get the name of the most-derived type at the specified offset. + const char *getSubobjectTypeName() const { return SubobjectTypeName; } +}; + +/// \brief Get information about the dynamic type of an object. +DynamicTypeInfo getDynamicTypeInfo(void *Object); + +/// \brief Check whether the dynamic type of \p Object has a \p Type subobject +/// at offset 0. +/// \return \c true if the type matches, \c false if not. +bool checkDynamicType(void *Object, void *Type, HashValue Hash); + +const unsigned VptrTypeCacheSize = 128; + +/// \brief A cache of the results of checkDynamicType. \c checkDynamicType would +/// return \c true (modulo hash collisions) if +/// \code +/// __ubsan_vptr_type_cache[Hash % VptrTypeCacheSize] == Hash +/// \endcode +extern "C" SANITIZER_INTERFACE_ATTRIBUTE +HashValue __ubsan_vptr_type_cache[VptrTypeCacheSize]; + +} // namespace __ubsan + +#endif // UBSAN_TYPE_HASH_H diff --git a/lib/ubsan/ubsan_value.cc b/lib/ubsan/ubsan_value.cc new file mode 100644 index 000000000000..f17c58989db9 --- /dev/null +++ b/lib/ubsan/ubsan_value.cc @@ -0,0 +1,81 @@ +//===-- ubsan_value.cc ----------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Representation of a runtime value, as marshaled from the generated code to +// the ubsan runtime. +// +//===----------------------------------------------------------------------===// + +#include "ubsan_value.h" + +using namespace __ubsan; + +SIntMax Value::getSIntValue() const { + CHECK(getType().isSignedIntegerTy()); + if (isInlineInt()) { + // Val was zero-extended to ValueHandle. Sign-extend from original width + // to SIntMax. + const unsigned ExtraBits = + sizeof(SIntMax) * 8 - getType().getIntegerBitWidth(); + return SIntMax(Val) << ExtraBits >> ExtraBits; + } + if (getType().getIntegerBitWidth() == 64) + return *reinterpret_cast<s64*>(Val); +#if HAVE_INT128_T + if (getType().getIntegerBitWidth() == 128) + return *reinterpret_cast<s128*>(Val); +#else + if (getType().getIntegerBitWidth() == 128) + UNREACHABLE("libclang_rt.ubsan was built without __int128 support"); +#endif + UNREACHABLE("unexpected bit width"); +} + +UIntMax Value::getUIntValue() const { + CHECK(getType().isUnsignedIntegerTy()); + if (isInlineInt()) + return Val; + if (getType().getIntegerBitWidth() == 64) + return *reinterpret_cast<u64*>(Val); +#if HAVE_INT128_T + if (getType().getIntegerBitWidth() == 128) + return *reinterpret_cast<u128*>(Val); +#else + if (getType().getIntegerBitWidth() == 128) + UNREACHABLE("libclang_rt.ubsan was built without __int128 support"); +#endif + UNREACHABLE("unexpected bit width"); +} + +UIntMax Value::getPositiveIntValue() const { + if (getType().isUnsignedIntegerTy()) + return getUIntValue(); + SIntMax Val = getSIntValue(); + CHECK(Val >= 0); + return Val; +} + +/// Get the floating-point value of this object, extended to a long double. +/// These are always passed by address (our calling convention doesn't allow +/// them to be passed in floating-point registers, so this has little cost). +FloatMax Value::getFloatValue() const { + CHECK(getType().isFloatTy()); + switch (getType().getFloatBitWidth()) { +#if 0 + // FIXME: OpenCL / NEON 'half' type. LLVM can't lower the conversion + // from this to 'long double'. + case 16: return *reinterpret_cast<__fp16*>(Val); +#endif + case 32: return *reinterpret_cast<float*>(Val); + case 64: return *reinterpret_cast<double*>(Val); + case 80: return *reinterpret_cast<long double*>(Val); + case 128: return *reinterpret_cast<long double*>(Val); + } + UNREACHABLE("unexpected floating point bit width"); +} diff --git a/lib/ubsan/ubsan_value.h b/lib/ubsan/ubsan_value.h new file mode 100644 index 000000000000..e673f7af1d83 --- /dev/null +++ b/lib/ubsan/ubsan_value.h @@ -0,0 +1,195 @@ +//===-- ubsan_value.h -------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Representation of data which is passed from the compiler-generated calls into +// the ubsan runtime. +// +//===----------------------------------------------------------------------===// +#ifndef UBSAN_VALUE_H +#define UBSAN_VALUE_H + +// For now, only support linux and darwin. Other platforms should be easy to +// add, and probably work as-is. +#if !defined(__linux__) && !defined(__APPLE__) +#error "UBSan not supported for this platform!" +#endif + +#include "sanitizer_common/sanitizer_atomic.h" +#include "sanitizer_common/sanitizer_common.h" + +// FIXME: Move this out to a config header. +#if __SIZEOF_INT128__ +typedef __int128 s128; +typedef unsigned __int128 u128; +#define HAVE_INT128_T 1 +#else +#define HAVE_INT128_T 0 +#endif + + +namespace __ubsan { + +/// \brief Largest integer types we support. +#if HAVE_INT128_T +typedef s128 SIntMax; +typedef u128 UIntMax; +#else +typedef s64 SIntMax; +typedef u64 UIntMax; +#endif + +/// \brief Largest floating-point type we support. +typedef long double FloatMax; + +/// \brief A description of a source location. This corresponds to Clang's +/// \c PresumedLoc type. +class SourceLocation { + const char *Filename; + u32 Line; + u32 Column; + +public: + SourceLocation() : Filename(), Line(), Column() {} + SourceLocation(const char *Filename, unsigned Line, unsigned Column) + : Filename(Filename), Line(Line), Column(Column) {} + + /// \brief Determine whether the source location is known. + bool isInvalid() const { return !Filename; } + + /// \brief Atomically acquire a copy, disabling original in-place. + /// Exactly one call to acquire() returns a copy that isn't disabled. + SourceLocation acquire() { + u32 OldColumn = __sanitizer::atomic_exchange( + (__sanitizer::atomic_uint32_t *)&Column, ~u32(0), + __sanitizer::memory_order_relaxed); + return SourceLocation(Filename, Line, OldColumn); + } + + /// \brief Determine if this Location has been disabled. + /// Disabled SourceLocations are invalid to use. + bool isDisabled() { + return Column == ~u32(0); + } + + /// \brief Get the presumed filename for the source location. + const char *getFilename() const { return Filename; } + /// \brief Get the presumed line number. + unsigned getLine() const { return Line; } + /// \brief Get the column within the presumed line. + unsigned getColumn() const { return Column; } +}; + + +/// \brief A description of a type. +class TypeDescriptor { + /// A value from the \c Kind enumeration, specifying what flavor of type we + /// have. + u16 TypeKind; + + /// A \c Type-specific value providing information which allows us to + /// interpret the meaning of a ValueHandle of this type. + u16 TypeInfo; + + /// The name of the type follows, in a format suitable for including in + /// diagnostics. + char TypeName[1]; + +public: + enum Kind { + /// An integer type. Lowest bit is 1 for a signed value, 0 for an unsigned + /// value. Remaining bits are log_2(bit width). The value representation is + /// the integer itself if it fits into a ValueHandle, and a pointer to the + /// integer otherwise. + TK_Integer = 0x0000, + /// A floating-point type. Low 16 bits are bit width. The value + /// representation is a pointer to the floating-point value. + TK_Float = 0x0001, + /// Any other type. The value representation is unspecified. + TK_Unknown = 0xffff + }; + + const char *getTypeName() const { return TypeName; } + + Kind getKind() const { + return static_cast<Kind>(TypeKind); + } + + bool isIntegerTy() const { return getKind() == TK_Integer; } + bool isSignedIntegerTy() const { + return isIntegerTy() && (TypeInfo & 1); + } + bool isUnsignedIntegerTy() const { + return isIntegerTy() && !(TypeInfo & 1); + } + unsigned getIntegerBitWidth() const { + CHECK(isIntegerTy()); + return 1 << (TypeInfo >> 1); + } + + bool isFloatTy() const { return getKind() == TK_Float; } + unsigned getFloatBitWidth() const { + CHECK(isFloatTy()); + return TypeInfo; + } +}; + +/// \brief An opaque handle to a value. +typedef uptr ValueHandle; + + +/// \brief Representation of an operand value provided by the instrumented code. +/// +/// This is a combination of a TypeDescriptor (which is emitted as constant data +/// as an operand to a handler function) and a ValueHandle (which is passed at +/// runtime when a check failure occurs). +class Value { + /// The type of the value. + const TypeDescriptor &Type; + /// The encoded value itself. + ValueHandle Val; + + /// Is \c Val a (zero-extended) integer? + bool isInlineInt() const { + CHECK(getType().isIntegerTy()); + const unsigned InlineBits = sizeof(ValueHandle) * 8; + const unsigned Bits = getType().getIntegerBitWidth(); + return Bits <= InlineBits; + } + +public: + Value(const TypeDescriptor &Type, ValueHandle Val) : Type(Type), Val(Val) {} + + const TypeDescriptor &getType() const { return Type; } + + /// \brief Get this value as a signed integer. + SIntMax getSIntValue() const; + + /// \brief Get this value as an unsigned integer. + UIntMax getUIntValue() const; + + /// \brief Decode this value, which must be a positive or unsigned integer. + UIntMax getPositiveIntValue() const; + + /// Is this an integer with value -1? + bool isMinusOne() const { + return getType().isSignedIntegerTy() && getSIntValue() == -1; + } + + /// Is this a negative integer? + bool isNegative() const { + return getType().isSignedIntegerTy() && getSIntValue() < 0; + } + + /// \brief Get this value as a floating-point quantity. + FloatMax getFloatValue() const; +}; + +} // namespace __ubsan + +#endif // UBSAN_VALUE_H diff --git a/lib/ucmpdi2.c b/lib/ucmpdi2.c index 3242bbf08046..40af23613b1f 100644 --- a/lib/ucmpdi2.c +++ b/lib/ucmpdi2.c @@ -36,3 +36,16 @@ __ucmpdi2(du_int a, du_int b) return 2; return 1; } + +#ifdef __ARM_EABI__ +/* Returns: if (a < b) returns -1 +* if (a == b) returns 0 +* if (a > b) returns 1 +*/ +COMPILER_RT_ABI si_int +__aeabi_ulcmp(di_int a, di_int b) +{ + return __ucmpdi2(a, b) - 1; +} +#endif + diff --git a/make/AppleBI.mk b/make/AppleBI.mk index 96f8222c7b5c..b5e702b10e66 100644 --- a/make/AppleBI.mk +++ b/make/AppleBI.mk @@ -64,8 +64,7 @@ $(OBJROOT)/libcompiler_rt-%.dylib : $(OBJROOT)/darwin_bni/Release/%/libcompiler_ $(SYMROOT)/libcompiler_rt.dylib: $(foreach arch,$(filter-out armv4t,$(RC_ARCHS)), \ $(OBJROOT)/libcompiler_rt-$(arch).dylib) $(call GetCNAVar,LIPO,Platform.darwin_bni,Release,) -create $^ -o $@ - - + $(call GetCNAVar,DSYMUTIL,Platform.darwin_bni,Release,) $@ # Copy results to DSTROOT. diff --git a/make/config.mk b/make/config.mk index 42fb9a8768f2..6398d058de76 100644 --- a/make/config.mk +++ b/make/config.mk @@ -21,6 +21,7 @@ MKDIR := mkdir -p DATE := date LIPO := lipo CP := cp +DSYMUTIL := dsymutil VERBOSE := 0 DEBUGMAKE := @@ -42,5 +43,5 @@ endif ### # Common compiler options -COMMON_CXXFLAGS=-fno-exceptions -fPIC -funwind-tables -I${ProjSrcRoot}/lib +COMMON_CXXFLAGS=-fno-exceptions -fPIC -funwind-tables -I${ProjSrcRoot}/lib -I${ProjSrcRoot}/include COMMON_CFLAGS=-fPIC diff --git a/make/lib_info.mk b/make/lib_info.mk index 2e85f6402b2c..31850f78f981 100644 --- a/make/lib_info.mk +++ b/make/lib_info.mk @@ -53,8 +53,7 @@ $(foreach key,$(SubDirKeys),\ # The names of all the available options. AvailableOptions := AR ARFLAGS \ - CC CFLAGS FUNCTIONS OPTIMIZED \ + CC CFLAGS LDFLAGS FUNCTIONS OPTIMIZED \ RANLIB RANLIBFLAGS \ - VISIBILITY_HIDDEN \ - KERNEL_USE \ - STRIP LIPO + VISIBILITY_HIDDEN KERNEL_USE \ + SHARED_LIBRARY SHARED_LIBRARY_SUFFIX STRIP LIPO DSYMUTIL diff --git a/make/options.mk b/make/options.mk index f695fc8db717..67197de03f47 100644 --- a/make/options.mk +++ b/make/options.mk @@ -23,14 +23,26 @@ OPTIMIZED := 1 # default. VISIBILITY_HIDDEN := 0 +# Whether the library is being built for kernel use. +KERNEL_USE := 0 + +# Whether the library should be built as a shared object. +SHARED_LIBRARY := 0 + # Miscellaneous tools. AR := ar # FIXME: Remove these pipes once ranlib errors are fixed. ARFLAGS := cru 2> /dev/null + +LDFLAGS := + RANLIB := ranlib # FIXME: Remove these pipes once ranlib errors are fixed. RANLIBFLAGS := 2> /dev/null STRIP := strip LIPO := lipo +DSYMUTIL := dsymutil + +SHARED_LIBRARY_SUFFIX := so diff --git a/make/platform/clang_darwin.mk b/make/platform/clang_darwin.mk index d1788c44903a..fe84a0565929 100644 --- a/make/platform/clang_darwin.mk +++ b/make/platform/clang_darwin.mk @@ -44,6 +44,11 @@ UniversalArchs.eprintf := $(call CheckArches,i386,eprintf) Configs += 10.4 UniversalArchs.10.4 := $(call CheckArches,i386 x86_64,10.4) +# Configuration for targetting iOS for a couple of functions that didn't +# make it into libSystem. +Configs += ios +UniversalArchs.ios := $(call CheckArches,i386 x86_64 armv7,ios) + # Configuration for targetting OSX. These functions may not be in libSystem # so we should provide our own. Configs += osx @@ -51,16 +56,40 @@ UniversalArchs.osx := $(call CheckArches,i386 x86_64,osx) # Configuration for use with kernel/kexts. Configs += cc_kext -UniversalArchs.cc_kext := $(call CheckArches,i386 x86_64,cc_kext) +UniversalArchs.cc_kext := $(call CheckArches,armv7 i386 x86_64,cc_kext) + +# Configuration for use with kernel/kexts for iOS 5.0 and earlier (which used +# a different code generation strategy). +Configs += cc_kext_ios5 +UniversalArchs.cc_kext_ios5 := $(call CheckArches,x86_64 armv7,cc_kext_ios5) # Configurations which define the profiling support functions. Configs += profile_osx UniversalArchs.profile_osx := $(call CheckArches,i386 x86_64,profile_osx) +Configs += profile_ios +UniversalArchs.profile_ios := $(call CheckArches,i386 x86_64 armv7,profile_ios) # Configurations which define the ASAN support functions. Configs += asan_osx UniversalArchs.asan_osx := $(call CheckArches,i386 x86_64,asan_osx) +Configs += asan_osx_dynamic +UniversalArchs.asan_osx_dynamic := $(call CheckArches,i386 x86_64,asan_osx_dynamic) + +Configs += ubsan_osx +UniversalArchs.ubsan_osx := $(call CheckArches,i386 x86_64,ubsan_osx) + +# Darwin 10.6 has a bug in cctools that makes it unable to use ranlib on our ARM +# object files. If we are on that platform, strip out all ARM archs. We still +# build the libraries themselves so that Clang can find them where it expects +# them, even though they might not have an expected slice. +ifneq ($(shell sw_vers -productVersion | grep 10.6),) +UniversalArchs.ios := $(filter-out armv7, $(UniversalArchs.ios)) +UniversalArchs.cc_kext := $(filter-out armv7, $(UniversalArchs.cc_kext)) +UniversalArchs.cc_kext_ios5 := $(filter-out armv7, $(UniversalArchs.cc_kext_ios5)) +UniversalArchs.profile_ios := $(filter-out armv7, $(UniversalArchs.profile_ios)) +endif + # If RC_SUPPORTED_ARCHS is defined, treat it as a list of the architectures we # are intended to support and limit what we try to build to that. # @@ -87,31 +116,79 @@ CFLAGS := -Wall -Werror -O3 -fomit-frame-pointer # supported deployment target -- nothing in the compiler-rt libraries should # actually depend on the deployment target. OSX_DEPLOYMENT_ARGS := -mmacosx-version-min=10.4 +IOS_DEPLOYMENT_ARGS := -miphoneos-version-min=1.0 +IOS6_DEPLOYMENT_ARGS := -miphoneos-version-min=6.0 +IOSSIM_DEPLOYMENT_ARGS := -miphoneos-version-min=1.0 # Use our stub SDK as the sysroot to support more portable building. OSX_DEPLOYMENT_ARGS += -isysroot $(ProjSrcRoot)/SDKs/darwin +IOS_DEPLOYMENT_ARGS += -isysroot $(ProjSrcRoot)/SDKs/darwin +IOS6_DEPLOYMENT_ARGS += -isysroot $(ProjSrcRoot)/SDKs/darwin +IOSSIM_DEPLOYMENT_ARGS += -isysroot $(ProjSrcRoot)/SDKs/darwin CFLAGS.eprintf := $(CFLAGS) $(OSX_DEPLOYMENT_ARGS) CFLAGS.10.4 := $(CFLAGS) $(OSX_DEPLOYMENT_ARGS) # FIXME: We can't build ASAN with our stub SDK yet. CFLAGS.asan_osx := $(CFLAGS) -mmacosx-version-min=10.5 -fno-builtin - +CFLAGS.asan_osx_dynamic := \ + $(CFLAGS) -mmacosx-version-min=10.5 -fno-builtin \ + -DMAC_INTERPOSE_FUNCTIONS=1 + +CFLAGS.ubsan_osx := $(CFLAGS) -mmacosx-version-min=10.5 -fno-builtin + +CFLAGS.ios.i386 := $(CFLAGS) $(IOSSIM_DEPLOYMENT_ARGS) +CFLAGS.ios.x86_64 := $(CFLAGS) $(IOSSIM_DEPLOYMENT_ARGS) +CFLAGS.ios.armv7 := $(CFLAGS) $(IOS_DEPLOYMENT_ARGS) +CFLAGS.ios.armv7f := $(CFLAGS) $(IOS_DEPLOYMENT_ARGS) +CFLAGS.ios.armv7k := $(CFLAGS) $(IOS_DEPLOYMENT_ARGS) +CFLAGS.ios.armv7s := $(CFLAGS) $(IOS_DEPLOYMENT_ARGS) CFLAGS.osx.i386 := $(CFLAGS) $(OSX_DEPLOYMENT_ARGS) CFLAGS.osx.x86_64 := $(CFLAGS) $(OSX_DEPLOYMENT_ARGS) CFLAGS.cc_kext.i386 := $(CFLAGS) $(OSX_DEPLOYMENT_ARGS) CFLAGS.cc_kext.x86_64 := $(CFLAGS) $(OSX_DEPLOYMENT_ARGS) +CFLAGS.cc_kext.armv7 := $(CFLAGS) $(IOS6_DEPLOYMENT_ARGS) +CFLAGS.cc_kext.armv7f := $(CFLAGS) $(IOS6_DEPLOYMENT_ARGS) +CFLAGS.cc_kext.armv7k := $(CFLAGS) $(IOS6_DEPLOYMENT_ARGS) +CFLAGS.cc_kext.armv7s := $(CFLAGS) $(IOS6_DEPLOYMENT_ARGS) +CFLAGS.cc_kext_ios5.armv7 := $(CFLAGS) $(IOS_DEPLOYMENT_ARGS) +CFLAGS.cc_kext_ios5.armv7f := $(CFLAGS) $(IOS_DEPLOYMENT_ARGS) +CFLAGS.cc_kext_ios5.armv7k := $(CFLAGS) $(IOS_DEPLOYMENT_ARGS) +CFLAGS.cc_kext_ios5.armv7s := $(CFLAGS) $(IOS_DEPLOYMENT_ARGS) CFLAGS.profile_osx.i386 := $(CFLAGS) $(OSX_DEPLOYMENT_ARGS) CFLAGS.profile_osx.x86_64 := $(CFLAGS) $(OSX_DEPLOYMENT_ARGS) +CFLAGS.profile_ios.i386 := $(CFLAGS) $(IOSSIM_DEPLOYMENT_ARGS) +CFLAGS.profile_ios.x86_64 := $(CFLAGS) $(IOSSIM_DEPLOYMENT_ARGS) +CFLAGS.profile_ios.armv7 := $(CFLAGS) $(IOS_DEPLOYMENT_ARGS) +CFLAGS.profile_ios.armv7f := $(CFLAGS) $(IOS_DEPLOYMENT_ARGS) +CFLAGS.profile_ios.armv7k := $(CFLAGS) $(IOS_DEPLOYMENT_ARGS) +CFLAGS.profile_ios.armv7s := $(CFLAGS) $(IOS_DEPLOYMENT_ARGS) + +# Configure the asan_osx_dynamic library to be built shared. +SHARED_LIBRARY.asan_osx_dynamic := 1 +LDFLAGS.asan_osx_dynamic := -framework Foundation -lstdc++ FUNCTIONS.eprintf := eprintf FUNCTIONS.10.4 := eprintf floatundidf floatundisf floatundixf +FUNCTIONS.ios := divmodsi4 udivmodsi4 mulosi4 mulodi4 muloti4 +# On x86, the divmod functions reference divsi. +FUNCTIONS.ios.i386 := $(FUNCTIONS.ios) \ + divsi3 udivsi3 +FUNCTIONS.ios.x86_64 := $(FUNCTIONS.ios) \ + divsi3 udivsi3 + FUNCTIONS.osx := mulosi4 mulodi4 muloti4 FUNCTIONS.profile_osx := GCDAProfiling +FUNCTIONS.profile_ios := GCDAProfiling FUNCTIONS.asan_osx := $(AsanFunctions) $(InterceptionFunctions) \ $(SanitizerCommonFunctions) +FUNCTIONS.asan_osx_dynamic := $(AsanFunctions) $(InterceptionFunctions) \ + $(SanitizerCommonFunctions) \ + $(AsanDynamicFunctions) + +FUNCTIONS.ubsan_osx := $(UbsanFunctions) $(SanitizerCommonFunctions) CCKEXT_COMMON_FUNCTIONS := \ absvdi2 \ @@ -224,6 +301,49 @@ CCKEXT_ARM_FUNCTIONS := $(CCKEXT_COMMON_FUNCTIONS) \ unorddf2 \ unordsf2 +CCKEXT_ARMVFP_FUNCTIONS := $(CCKEXT_ARM_FUNCTIONS) \ + adddf3vfp \ + addsf3vfp \ + divdf3vfp \ + divsf3vfp \ + eqdf2vfp \ + eqsf2vfp \ + extendsfdf2vfp \ + fixdfsivfp \ + fixsfsivfp \ + fixunsdfsivfp \ + fixunssfsivfp \ + floatsidfvfp \ + floatsisfvfp \ + floatunssidfvfp \ + floatunssisfvfp \ + gedf2vfp \ + gesf2vfp \ + gtdf2vfp \ + gtsf2vfp \ + ledf2vfp \ + lesf2vfp \ + ltdf2vfp \ + ltsf2vfp \ + muldf3vfp \ + mulsf3vfp \ + nedf2vfp \ + nesf2vfp \ + subdf3vfp \ + subsf3vfp \ + truncdfsf2vfp \ + unorddf2vfp \ + unordsf2vfp + +FUNCTIONS.cc_kext.armv7 := $(CCKEXT_ARMVFP_FUNCTIONS) +FUNCTIONS.cc_kext.armv7f := $(CCKEXT_ARMVFP_FUNCTIONS) +FUNCTIONS.cc_kext.armv7k := $(CCKEXT_ARMVFP_FUNCTIONS) +FUNCTIONS.cc_kext.armv7s := $(CCKEXT_ARMVFP_FUNCTIONS) +FUNCTIONS.cc_kext_ios5.armv7 := $(CCKEXT_ARMVFP_FUNCTIONS) +FUNCTIONS.cc_kext_ios5.armv7f := $(CCKEXT_ARMVFP_FUNCTIONS) +FUNCTIONS.cc_kext_ios5.armv7k := $(CCKEXT_ARMVFP_FUNCTIONS) +FUNCTIONS.cc_kext_ios5.armv7s := $(CCKEXT_ARMVFP_FUNCTIONS) + CCKEXT_X86_FUNCTIONS := $(CCKEXT_COMMON_FUNCTIONS) \ divxc3 \ fixunsxfdi \ @@ -297,11 +417,30 @@ CCKEXT_MISSING_FUNCTIONS := \ aeabi_fcmpge aeabi_fcmpgt aeabi_fcmple aeabi_fcmplt aeabi_frsub aeabi_idivmod \ aeabi_uidivmod +FUNCTIONS.cc_kext.armv7 := \ + $(filter-out $(CCKEXT_MISSING_FUNCTIONS),$(FUNCTIONS.cc_kext.armv7)) +FUNCTIONS.cc_kext.armv7f := \ + $(filter-out $(CCKEXT_MISSING_FUNCTIONS),$(FUNCTIONS.cc_kext.armv7f)) +FUNCTIONS.cc_kext.armv7k := \ + $(filter-out $(CCKEXT_MISSING_FUNCTIONS),$(FUNCTIONS.cc_kext.armv7k)) +FUNCTIONS.cc_kext.armv7s := \ + $(filter-out $(CCKEXT_MISSING_FUNCTIONS),$(FUNCTIONS.cc_kext.armv7s)) +FUNCTIONS.cc_kext_ios5.armv7 := \ + $(filter-out $(CCKEXT_MISSING_FUNCTIONS),$(FUNCTIONS.cc_kext_ios5.armv7)) +FUNCTIONS.cc_kext_ios5.armv7f := \ + $(filter-out $(CCKEXT_MISSING_FUNCTIONS),$(FUNCTIONS.cc_kext_ios5.armv7f)) +FUNCTIONS.cc_kext_ios5.armv7k := \ + $(filter-out $(CCKEXT_MISSING_FUNCTIONS),$(FUNCTIONS.cc_kext_ios5.armv7k)) +FUNCTIONS.cc_kext_ios5.armv7s := \ + $(filter-out $(CCKEXT_MISSING_FUNCTIONS),$(FUNCTIONS.cc_kext_ios5.armv7s)) FUNCTIONS.cc_kext.i386 := \ $(filter-out $(CCKEXT_MISSING_FUNCTIONS),$(FUNCTIONS.cc_kext.i386)) FUNCTIONS.cc_kext.x86_64 := \ $(filter-out $(CCKEXT_MISSING_FUNCTIONS),$(FUNCTIONS.cc_kext.x86_64)) KERNEL_USE.cc_kext := 1 +KERNEL_USE.cc_kext_ios5 := 1 VISIBILITY_HIDDEN := 1 + +SHARED_LIBRARY_SUFFIX := dylib diff --git a/make/platform/clang_linux.mk b/make/platform/clang_linux.mk index f2b049f8c931..adfe8917de87 100644 --- a/make/platform/clang_linux.mk +++ b/make/platform/clang_linux.mk @@ -18,42 +18,63 @@ $(error "unable to infer compiler target triple for $(CC)") endif endif -CompilerTargetArch := $(firstword $(subst -, ,$(CompilerTargetTriple))) - # Only define configs if we detected a linux target. ifneq ($(findstring -linux-,$(CompilerTargetTriple)),) -# Configurations which just include all the runtime functions. +# Define configs only if arch in triple is i386 or x86_64 +CompilerTargetArch := $(firstword $(subst -, ,$(CompilerTargetTriple))) ifeq ($(call contains,i386 x86_64,$(CompilerTargetArch)),true) -Configs += full-i386 full-x86_64 -Arch.full-i386 := i386 -Arch.full-x86_64 := x86_64 -endif -# Configuration for profile runtime. -ifeq ($(call contains,i386 x86_64,$(CompilerTargetArch)),true) -Configs += profile-i386 profile-x86_64 -Arch.profile-i386 := i386 -Arch.profile-x86_64 := x86_64 +# TryCompile compiler source flags +# Returns exit code of running a compiler invocation. +TryCompile = \ + $(shell \ + cflags=""; \ + for flag in $(3); do \ + cflags="$$cflags $$flag"; \ + done; \ + $(1) $$cflags $(2) -o /dev/null > /dev/null 2> /dev/null ; \ + echo $$?) + +test_source = $(ProjSrcRoot)/make/platform/clang_linux_test_input.c +ifeq ($(CompilerTargetArch),i386) + SupportedArches := i386 + ifeq ($(call TryCompile,$(CC),$(test_source),-m64),0) + SupportedArches += x86_64 + endif +else + SupportedArches := x86_64 + ifeq ($(call TryCompile,$(CC),$(test_source),-m32),0) + SupportedArches += i386 + endif endif -# Configuration for ASAN runtime. -ifeq ($(CompilerTargetArch),i386) -Configs += asan-i386 +# Build runtime libraries for i386. +ifeq ($(call contains,$(SupportedArches),i386),true) +Configs += full-i386 profile-i386 asan-i386 ubsan-i386 +Arch.full-i386 := i386 +Arch.profile-i386 := i386 Arch.asan-i386 := i386 +Arch.ubsan-i386 := i386 endif -ifeq ($(CompilerTargetArch),x86_64) -Configs += asan-x86_64 + +# Build runtime libraries for x86_64. +ifeq ($(call contains,$(SupportedArches),x86_64),true) +Configs += full-x86_64 profile-x86_64 asan-x86_64 tsan-x86_64 ubsan-x86_64 +Arch.full-x86_64 := x86_64 +Arch.profile-x86_64 := x86_64 Arch.asan-x86_64 := x86_64 +Arch.tsan-x86_64 := x86_64 +Arch.ubsan-x86_64 := x86_64 endif -# Configuration for TSAN runtime. -ifeq ($(CompilerTargetArch),x86_64) -Configs += tsan-x86_64 -Arch.tsan-x86_64 := x86_64 +ifneq ($(LLVM_ANDROID_TOOLCHAIN_DIR),) +Configs += asan-arm-android +Arch.asan-arm-android := arm-android endif endif +endif ### @@ -66,6 +87,17 @@ CFLAGS.profile-x86_64 := $(CFLAGS) -m64 CFLAGS.asan-i386 := $(CFLAGS) -m32 -fPIE -fno-builtin CFLAGS.asan-x86_64 := $(CFLAGS) -m64 -fPIE -fno-builtin CFLAGS.tsan-x86_64 := $(CFLAGS) -m64 -fPIE -fno-builtin +CFLAGS.ubsan-i386 := $(CFLAGS) -m32 -fPIE -fno-builtin +CFLAGS.ubsan-x86_64 := $(CFLAGS) -m64 -fPIE -fno-builtin + +SHARED_LIBRARY.asan-arm-android := 1 +ANDROID_COMMON_FLAGS := -target arm-linux-androideabi \ + --sysroot=$(LLVM_ANDROID_TOOLCHAIN_DIR)/sysroot \ + -B$(LLVM_ANDROID_TOOLCHAIN_DIR) +CFLAGS.asan-arm-android := $(CFLAGS) -fPIC -fno-builtin \ + $(ANDROID_COMMON_FLAGS) -mllvm -arm-enable-ehabi +LDFLAGS.asan-arm-android := $(LDFLAGS) $(ANDROID_COMMON_FLAGS) -ldl \ + -Wl,-soname=libclang_rt.asan-arm-android.so # Use our stub SDK as the sysroot to support more portable building. For now we # just do this for the non-ASAN modules, because the stub SDK doesn't have @@ -83,11 +115,17 @@ FUNCTIONS.asan-i386 := $(AsanFunctions) $(InterceptionFunctions) \ $(SanitizerCommonFunctions) FUNCTIONS.asan-x86_64 := $(AsanFunctions) $(InterceptionFunctions) \ $(SanitizerCommonFunctions) +FUNCTIONS.asan-arm-android := $(AsanFunctions) $(InterceptionFunctions) \ + $(SanitizerCommonFunctions) FUNCTIONS.tsan-x86_64 := $(TsanFunctions) $(InterceptionFunctions) \ - $(SanitizerCommonFunctions) + $(SanitizerCommonFunctions) +FUNCTIONS.ubsan-i386 := $(UbsanFunctions) $(SanitizerCommonFunctions) +FUNCTIONS.ubsan-x86_64 := $(UbsanFunctions) $(SanitizerCommonFunctions) # Always use optimized variants. OPTIMIZED := 1 # We don't need to use visibility hidden on Linux. VISIBILITY_HIDDEN := 0 + +SHARED_LIBRARY_SUFFIX := so diff --git a/make/platform/clang_linux_test_input.c b/make/platform/clang_linux_test_input.c new file mode 100644 index 000000000000..e65ce9860af4 --- /dev/null +++ b/make/platform/clang_linux_test_input.c @@ -0,0 +1,4 @@ +// This file is used to check if we can produce working executables +// for i386 and x86_64 archs on Linux. +#include <stdlib.h> +int main(){} diff --git a/make/platform/darwin_bni.mk b/make/platform/darwin_bni.mk index 477e072de0a0..d12cfdff7040 100644 --- a/make/platform/darwin_bni.mk +++ b/make/platform/darwin_bni.mk @@ -14,6 +14,7 @@ ifneq (,$(SDKROOT)) RANLIB := $(shell xcrun -sdk $(SDKROOT) -find ranlib) STRIP := $(shell xcrun -sdk $(SDKROOT) -find strip) LIPO := $(shell xcrun -sdk $(SDKROOT) -find lipo) + DSYMUTIL := $(shell xcrun -sdk $(SDKROOT) -find dsymutil) endif ifneq ($(IPHONEOS_DEPLOYMENT_TARGET),) @@ -111,3 +112,6 @@ FUNCTIONS.armv7 := $(FUNCTIONS) \ nedf2vfp nesf2vfp \ subdf3vfp subsf3vfp truncdfsf2vfp unorddf2vfp unordsf2vfp \ modsi3 umodsi3 udivsi3 divsi3 udivmodsi4 divmodsi4 + +FUNCTIONS.armv7s := $(FUNCTIONS.armv7) + diff --git a/test/Unit/endianness.h b/test/Unit/endianness.h index 669e6f1736a9..06c53de0bfa9 100644 --- a/test/Unit/endianness.h +++ b/test/Unit/endianness.h @@ -51,6 +51,21 @@ /* .. */ +#if defined(__OpenBSD__) || defined(__Bitrig__) +#include <machine/endian.h> + +#if _BYTE_ORDER == _BIG_ENDIAN +#define _YUGA_LITTLE_ENDIAN 0 +#define _YUGA_BIG_ENDIAN 1 +#elif _BYTE_ORDER == _LITTLE_ENDIAN +#define _YUGA_LITTLE_ENDIAN 1 +#define _YUGA_BIG_ENDIAN 0 +#endif /* _BYTE_ORDER */ + +#endif /* OpenBSD and Bitrig. */ + +/* .. */ + /* Mac OSX has __BIG_ENDIAN__ or __LITTLE_ENDIAN__ automatically set by the compiler (at least with GCC) */ #if defined(__APPLE__) && defined(__MACH__) || defined(__ellcc__ ) |