| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,138 @@ | ||
| //===-- memprof_thread.h ---------------------------------------*- C++ -*-===// | ||
| // | ||
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
| // See https://llvm.org/LICENSE.txt for license information. | ||
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
| // | ||
| // This file is a part of MemProfiler, a memory profiler. | ||
| // | ||
| // MemProf-private header for memprof_thread.cpp. | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #ifndef MEMPROF_THREAD_H | ||
| #define MEMPROF_THREAD_H | ||
|
|
||
| #include "memprof_allocator.h" | ||
| #include "memprof_internal.h" | ||
| #include "memprof_stats.h" | ||
| #include "sanitizer_common/sanitizer_common.h" | ||
| #include "sanitizer_common/sanitizer_libc.h" | ||
| #include "sanitizer_common/sanitizer_thread_registry.h" | ||
|
|
||
| namespace __sanitizer { | ||
| struct DTLS; | ||
| } // namespace __sanitizer | ||
|
|
||
| namespace __memprof { | ||
|
|
||
| const u32 kInvalidTid = 0xffffff; // Must fit into 24 bits. | ||
| const u32 kMaxNumberOfThreads = (1 << 22); // 4M | ||
|
|
||
| class MemprofThread; | ||
|
|
||
| // These objects are created for every thread and are never deleted, | ||
| // so we can find them by tid even if the thread is long dead. | ||
| struct MemprofThreadContext : public ThreadContextBase { | ||
| explicit MemprofThreadContext(int tid) | ||
| : ThreadContextBase(tid), announced(false), | ||
| destructor_iterations(GetPthreadDestructorIterations()), stack_id(0), | ||
| thread(nullptr) {} | ||
| bool announced; | ||
| u8 destructor_iterations; | ||
| u32 stack_id; | ||
| MemprofThread *thread; | ||
|
|
||
| void OnCreated(void *arg) override; | ||
| void OnFinished() override; | ||
|
|
||
| struct CreateThreadContextArgs { | ||
| MemprofThread *thread; | ||
| StackTrace *stack; | ||
| }; | ||
| }; | ||
|
|
||
| // MemprofThreadContext objects are never freed, so we need many of them. | ||
| COMPILER_CHECK(sizeof(MemprofThreadContext) <= 256); | ||
|
|
||
| // MemprofThread are stored in TSD and destroyed when the thread dies. | ||
| class MemprofThread { | ||
| public: | ||
| static MemprofThread *Create(thread_callback_t start_routine, void *arg, | ||
| u32 parent_tid, StackTrace *stack, | ||
| bool detached); | ||
| static void TSDDtor(void *tsd); | ||
| void Destroy(); | ||
|
|
||
| struct InitOptions; | ||
| void Init(const InitOptions *options = nullptr); | ||
|
|
||
| thread_return_t ThreadStart(tid_t os_id, | ||
| atomic_uintptr_t *signal_thread_is_registered); | ||
|
|
||
| uptr stack_top(); | ||
| uptr stack_bottom(); | ||
| uptr stack_size(); | ||
| uptr tls_begin() { return tls_begin_; } | ||
| uptr tls_end() { return tls_end_; } | ||
| DTLS *dtls() { return dtls_; } | ||
| u32 tid() { return context_->tid; } | ||
| MemprofThreadContext *context() { return context_; } | ||
| void set_context(MemprofThreadContext *context) { context_ = context; } | ||
|
|
||
| bool AddrIsInStack(uptr addr); | ||
|
|
||
| // True is this thread is currently unwinding stack (i.e. collecting a stack | ||
| // trace). Used to prevent deadlocks on platforms where libc unwinder calls | ||
| // malloc internally. See PR17116 for more details. | ||
| bool isUnwinding() const { return unwinding_; } | ||
| void setUnwinding(bool b) { unwinding_ = b; } | ||
|
|
||
| MemprofThreadLocalMallocStorage &malloc_storage() { return malloc_storage_; } | ||
| MemprofStats &stats() { return stats_; } | ||
|
|
||
| private: | ||
| // NOTE: There is no MemprofThread constructor. It is allocated | ||
| // via mmap() and *must* be valid in zero-initialized state. | ||
|
|
||
| void SetThreadStackAndTls(const InitOptions *options); | ||
|
|
||
| struct StackBounds { | ||
| uptr bottom; | ||
| uptr top; | ||
| }; | ||
| StackBounds GetStackBounds() const; | ||
|
|
||
| MemprofThreadContext *context_; | ||
| thread_callback_t start_routine_; | ||
| void *arg_; | ||
|
|
||
| uptr stack_top_; | ||
| uptr stack_bottom_; | ||
|
|
||
| uptr tls_begin_; | ||
| uptr tls_end_; | ||
| DTLS *dtls_; | ||
|
|
||
| MemprofThreadLocalMallocStorage malloc_storage_; | ||
| MemprofStats stats_; | ||
| bool unwinding_; | ||
| }; | ||
|
|
||
| // Returns a single instance of registry. | ||
| ThreadRegistry &memprofThreadRegistry(); | ||
|
|
||
| // Must be called under ThreadRegistryLock. | ||
| MemprofThreadContext *GetThreadContextByTidLocked(u32 tid); | ||
|
|
||
| // Get the current thread. May return 0. | ||
| MemprofThread *GetCurrentThread(); | ||
| void SetCurrentThread(MemprofThread *t); | ||
| u32 GetCurrentTidOrInvalid(); | ||
|
|
||
| // Used to handle fork(). | ||
| void EnsureMainThreadIDIsCorrect(); | ||
| } // namespace __memprof | ||
|
|
||
| #endif // MEMPROF_THREAD_H |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| ___memprof_default_options |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| set(MEMPROF_LIT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) | ||
|
|
||
| set(MEMPROF_TESTSUITES) | ||
| set(MEMPROF_DYNAMIC_TESTSUITES) | ||
|
|
||
| macro(get_bits_for_arch arch bits) | ||
| if (${arch} MATCHES "x86_64") | ||
| set(${bits} 64) | ||
| else() | ||
| message(FATAL_ERROR "Unexpected target architecture: ${arch}") | ||
| endif() | ||
| endmacro() | ||
|
|
||
| set(MEMPROF_TEST_DEPS ${SANITIZER_COMMON_LIT_TEST_DEPS}) | ||
| if(NOT COMPILER_RT_STANDALONE_BUILD) | ||
| list(APPEND MEMPROF_TEST_DEPS memprof) | ||
| if(COMPILER_RT_HAS_LLD AND TARGET lld) | ||
| list(APPEND MEMPROF_TEST_DEPS lld) | ||
| endif() | ||
| endif() | ||
| set(MEMPROF_DYNAMIC_TEST_DEPS ${MEMPROF_TEST_DEPS}) | ||
|
|
||
| set(MEMPROF_TEST_ARCH ${MEMPROF_SUPPORTED_ARCH}) | ||
|
|
||
| foreach(arch ${MEMPROF_TEST_ARCH}) | ||
| set(MEMPROF_TEST_TARGET_ARCH ${arch}) | ||
| string(TOLOWER "-${arch}-${OS_NAME}" MEMPROF_TEST_CONFIG_SUFFIX) | ||
| get_bits_for_arch(${arch} MEMPROF_TEST_BITS) | ||
| get_test_cc_for_arch(${arch} MEMPROF_TEST_TARGET_CC MEMPROF_TEST_TARGET_CFLAGS) | ||
| set(MEMPROF_TEST_DYNAMIC False) | ||
| string(TOUPPER ${arch} ARCH_UPPER_CASE) | ||
| set(CONFIG_NAME ${ARCH_UPPER_CASE}${OS_NAME}Config) | ||
| configure_lit_site_cfg( | ||
| ${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.py.in | ||
| ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME}/lit.site.cfg.py | ||
| ) | ||
| list(APPEND MEMPROF_TESTSUITES ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME}) | ||
|
|
||
| string(TOLOWER "-${arch}-${OS_NAME}-dynamic" MEMPROF_TEST_CONFIG_SUFFIX) | ||
| set(MEMPROF_TEST_DYNAMIC True) | ||
| set(CONFIG_NAME ${ARCH_UPPER_CASE}${OS_NAME}DynamicConfig) | ||
| configure_lit_site_cfg( | ||
| ${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.py.in | ||
| ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME}/lit.site.cfg.py) | ||
| list(APPEND MEMPROF_DYNAMIC_TESTSUITES | ||
| ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME}) | ||
| endforeach() | ||
|
|
||
| add_lit_testsuite(check-memprof "Running the MemProfiler tests" | ||
| ${MEMPROF_TESTSUITES} | ||
| DEPENDS ${MEMPROF_TEST_DEPS}) | ||
| set_target_properties(check-memprof PROPERTIES FOLDER "Compiler-RT Misc") | ||
|
|
||
| add_lit_testsuite(check-memprof-dynamic | ||
| "Running the MemProfiler tests with dynamic runtime" | ||
| ${MEMPROF_DYNAMIC_TESTSUITES} | ||
| ${exclude_from_check_all.g} | ||
| DEPENDS ${MEMPROF_DYNAMIC_TEST_DEPS}) | ||
| set_target_properties(check-memprof-dynamic | ||
| PROPERTIES FOLDER "Compiler-RT Misc") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| // Check atexit option. | ||
|
|
||
| // RUN: %clangxx_memprof -O0 %s -o %t | ||
| // RUN: %env_memprof_opts=atexit=1 %run %t 2>&1 | FileCheck %s | ||
| // RUN: %env_memprof_opts=atexit=0 %run %t 2>&1 | FileCheck %s --check-prefix=NOATEXIT | ||
|
|
||
| // CHECK: MemProfiler exit stats: | ||
| // CHECK: Stats: {{[0-9]+}}M malloced ({{[0-9]+}}M for overhead) by {{[0-9]+}} calls | ||
| // CHECK: Stats: {{[0-9]+}}M realloced by {{[0-9]+}} calls | ||
| // CHECK: Stats: {{[0-9]+}}M freed by {{[0-9]+}} calls | ||
| // CHECK: Stats: {{[0-9]+}}M really freed by {{[0-9]+}} calls | ||
| // CHECK: Stats: {{[0-9]+}}M ({{[0-9]+}}M-{{[0-9]+}}M) mmaped; {{[0-9]+}} maps, {{[0-9]+}} unmaps | ||
| // CHECK: mallocs by size class: | ||
| // CHECK: Stats: malloc large: {{[0-9]+}} | ||
|
|
||
| // NOATEXIT-NOT: MemProfiler exit stats | ||
|
|
||
| int main() { | ||
| return 0; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| // RUN: %clangxx_memprof -O2 %s -o %t && %run %t 2>&1 | FileCheck %s | ||
|
|
||
| const char *kMemProfDefaultOptions = "verbosity=1 help=1"; | ||
|
|
||
| extern "C" const char *__memprof_default_options() { | ||
| // CHECK: Available flags for MemProfiler: | ||
| return kMemProfDefaultOptions; | ||
| } | ||
|
|
||
| int main() { | ||
| return 0; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| // Check print_module_map option. | ||
|
|
||
| // RUN: %clangxx_memprof -O0 %s -o %t | ||
| // RUN: %env_memprof_opts=print_module_map=1 %run %t 2>&1 | FileCheck %s | ||
| // RUN: %env_memprof_opts=print_module_map=0 %run %t 2>&1 | FileCheck %s --check-prefix=NOMAP | ||
|
|
||
| // CHECK: Process memory map follows: | ||
| // CHECK: dump_process_map.cpp.tmp | ||
| // CHECK: End of process memory map. | ||
| // NOMAP-NOT: memory map | ||
|
|
||
| int main() { | ||
| return 0; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| // Check that free hook doesn't conflict with Realloc. | ||
| // RUN: %clangxx_memprof -O2 %s -o %t && %run %t 2>&1 | FileCheck %s | ||
|
|
||
| #include <sanitizer/allocator_interface.h> | ||
| #include <stdlib.h> | ||
| #include <unistd.h> | ||
|
|
||
| static void *glob_ptr; | ||
|
|
||
| extern "C" { | ||
| void __sanitizer_free_hook(const volatile void *ptr) { | ||
| if (ptr == glob_ptr) { | ||
| *(int *)ptr = 0; | ||
| write(1, "FreeHook\n", sizeof("FreeHook\n")); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| int main() { | ||
| int *x = (int *)malloc(100); | ||
| x[0] = 42; | ||
| glob_ptr = x; | ||
| int *y = (int *)realloc(x, 200); | ||
| // Verify that free hook was called and didn't spoil the memory. | ||
| if (y[0] != 42) { | ||
| _exit(1); | ||
| } | ||
| write(1, "Passed\n", sizeof("Passed\n")); | ||
| free(y); | ||
| // CHECK: FreeHook | ||
| // CHECK: Passed | ||
| return 0; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| // Check that user may include MemProf interface header. | ||
| // Also check that interfaces declared in the sanitizer's allocator_interface | ||
| // are defined for MemProf. | ||
| // RUN: %clang_memprof %s -o %t -DMEMPROF && %run %t | ||
| // RUN: %clang_memprof -x c %s -o %t -DMEMPROF && %run %t | ||
| // RUN: %clang %s -pie -o %t && %run %t | ||
| // RUN: %clang -x c %s -pie -o %t && %run %t | ||
| #include <sanitizer/allocator_interface.h> | ||
| #include <sanitizer/memprof_interface.h> | ||
| #include <stdlib.h> | ||
|
|
||
| int main() { | ||
| int *p = (int *)malloc(10 * sizeof(int)); | ||
| #ifdef MEMPROF | ||
| __sanitizer_get_estimated_allocated_size(8); | ||
| __sanitizer_get_ownership(p); | ||
| __sanitizer_get_allocated_size(p); | ||
| __sanitizer_get_current_allocated_bytes(); | ||
| __sanitizer_get_heap_size(); | ||
| __sanitizer_get_free_bytes(); | ||
| __sanitizer_get_unmapped_bytes(); | ||
| // malloc and free hooks are tested by the malloc_hook.cpp test. | ||
| #endif | ||
| return 0; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| // The for loop in the backticks below requires bash. | ||
| // REQUIRES: shell | ||
| // | ||
| // RUN: %clangxx_memprof %s -o %t | ||
|
|
||
| // Regular run. | ||
| // RUN: %run %t 2> %t.out | ||
| // RUN: FileCheck %s --check-prefix=CHECK-GOOD < %t.out | ||
|
|
||
| // Good log_path. | ||
| // RUN: rm -f %t.log.* | ||
| // RUN: %env_memprof_opts=log_path=%t.log %run %t 2> %t.out | ||
| // RUN: FileCheck %s --check-prefix=CHECK-GOOD < %t.log.* | ||
|
|
||
| // Invalid log_path. | ||
| // RUN: %env_memprof_opts=log_path=/dev/null/INVALID not %run %t 2> %t.out | ||
| // RUN: FileCheck %s --check-prefix=CHECK-INVALID < %t.out | ||
|
|
||
| // Too long log_path. | ||
| // RUN: %env_memprof_opts=log_path=`for((i=0;i<10000;i++)); do echo -n $i; done` \ | ||
| // RUN: not %run %t 2> %t.out | ||
| // RUN: FileCheck %s --check-prefix=CHECK-LONG < %t.out | ||
|
|
||
| #include <stdlib.h> | ||
| #include <string.h> | ||
| int main(int argc, char **argv) { | ||
| char *x = (char *)malloc(10); | ||
| memset(x, 0, 10); | ||
| free(x); | ||
| return 0; | ||
| } | ||
| // CHECK-GOOD: Memory allocation stack id | ||
| // CHECK-INVALID: ERROR: Can't open file: /dev/null/INVALID | ||
| // CHECK-LONG: ERROR: Path is too long: 01234 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| // RUN: %clangxx_memprof -O0 %s -o %t | ||
| // RUN: %env_memprof_opts=allocator_may_return_null=0 not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-SUMMARY | ||
| // RUN: %env_memprof_opts=allocator_may_return_null=1 %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-NULL | ||
| // Test print_summary | ||
| // RUN: %env_memprof_opts=allocator_may_return_null=0:print_summary=0 not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-NOSUMMARY | ||
|
|
||
| #include <stdio.h> | ||
| #include <stdlib.h> | ||
|
|
||
| static const size_t kMaxAllowedMallocSizePlusOne = (1ULL << 40) + 1; | ||
| int main() { | ||
| void *p = malloc(kMaxAllowedMallocSizePlusOne); | ||
| // CHECK: {{ERROR: MemProfiler: requested allocation size .* exceeds maximum supported size}} | ||
| // CHECK: {{#0 0x.* in .*malloc}} | ||
| // CHECK: {{#1 0x.* in main .*malloc-size-too-big.cpp:}}[[@LINE-3]] | ||
| // CHECK-SUMMARY: SUMMARY: MemProfiler: allocation-size-too-big | ||
| // CHECK-NOSUMMARY-NOT: SUMMARY: | ||
|
|
||
| printf("malloc returned: %zu\n", (size_t)p); | ||
| // CHECK-NULL: malloc returned: 0 | ||
|
|
||
| return 0; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| // Check that MemProf correctly handles malloc and free hooks. | ||
| // RUN: %clangxx_memprof -O2 %s -o %t && %run %t 2>&1 | FileCheck %s | ||
|
|
||
| #include <sanitizer/allocator_interface.h> | ||
| #include <stdlib.h> | ||
| #include <unistd.h> | ||
|
|
||
| extern "C" { | ||
| const volatile void *global_ptr; | ||
|
|
||
| #define WRITE(s) write(1, s, sizeof(s)) | ||
|
|
||
| // Note: avoid calling functions that allocate memory in malloc/free | ||
| // to avoid infinite recursion. | ||
| void __sanitizer_malloc_hook(const volatile void *ptr, size_t sz) { | ||
| if (__sanitizer_get_ownership(ptr) && sz == 4) { | ||
| WRITE("MallocHook\n"); | ||
| global_ptr = ptr; | ||
| } | ||
| } | ||
| void __sanitizer_free_hook(const volatile void *ptr) { | ||
| if (__sanitizer_get_ownership(ptr) && ptr == global_ptr) | ||
| WRITE("FreeHook\n"); | ||
| } | ||
| } // extern "C" | ||
|
|
||
| volatile int *x; | ||
|
|
||
| void MallocHook1(const volatile void *ptr, size_t sz) { WRITE("MH1\n"); } | ||
| void MallocHook2(const volatile void *ptr, size_t sz) { WRITE("MH2\n"); } | ||
| void FreeHook1(const volatile void *ptr) { WRITE("FH1\n"); } | ||
| void FreeHook2(const volatile void *ptr) { WRITE("FH2\n"); } | ||
| // Call this function with uninitialized arguments to poison | ||
| // TLS shadow for function parameters before calling operator | ||
| // new and, eventually, user-provided hook. | ||
| __attribute__((noinline)) void allocate(int *unused1, int *unused2) { | ||
| x = new int; | ||
| } | ||
|
|
||
| int main() { | ||
| __sanitizer_install_malloc_and_free_hooks(MallocHook1, FreeHook1); | ||
| __sanitizer_install_malloc_and_free_hooks(MallocHook2, FreeHook2); | ||
| int *undef1, *undef2; | ||
| allocate(undef1, undef2); | ||
| // CHECK: MallocHook | ||
| // CHECK: MH1 | ||
| // CHECK: MH2 | ||
| // Check that malloc hook was called with correct argument. | ||
| if (global_ptr != (void *)x) { | ||
| _exit(1); | ||
| } | ||
| *x = 0; | ||
| delete x; | ||
| // CHECK: FreeHook | ||
| // CHECK: FH1 | ||
| // CHECK: FH2 | ||
| return 0; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| // Check mem_info_cache_entries option. | ||
|
|
||
| // RUN: %clangxx_memprof -O0 %s -o %t && %env_memprof_opts=mem_info_cache_entries=15:print_mem_info_cache_miss_rate=1:print_mem_info_cache_miss_rate_details=1 %run %t 2>&1 | FileCheck %s | ||
|
|
||
| // CHECK: Set 14 miss rate: 0 / {{.*}} = 0.00% | ||
| // CHECK-NOT: Set | ||
|
|
||
| int main() { | ||
| return 0; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| // RUN: %clangxx_memprof -O0 %s -o %t && %env_memprof_opts=help=1 %run %t 2>&1 | FileCheck %s | ||
|
|
||
| int main() { | ||
| } | ||
|
|
||
| // CHECK: Available flags for MemProfiler: | ||
| // CHECK-DAG: atexit |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| // Check print_mem_info_cache_miss_rate and | ||
| // print_mem_info_cache_miss_rate_details options. | ||
|
|
||
| // RUN: %clangxx_memprof -O0 %s -o %t | ||
| // RUN: %env_memprof_opts=print_mem_info_cache_miss_rate=1 %run %t 2>&1 | FileCheck %s | ||
| // RUN: %env_memprof_opts=print_mem_info_cache_miss_rate=1:print_mem_info_cache_miss_rate_details=1 %run %t 2>&1 | FileCheck %s --check-prefix=DETAILS | ||
|
|
||
| // CHECK: Overall miss rate: 0 / {{.*}} = 0.00% | ||
| // DETAILS: Set 0 miss rate: 0 / {{.*}} = 0.00% | ||
| // DETAILS: Set 16380 miss rate: 0 / {{.*}} = 0.00% | ||
|
|
||
| int main() { | ||
| return 0; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| // RUN: %clangxx_memprof -O0 %s -o %t | ||
| // Default is true (free on realloc to 0 size) | ||
| // RUN: %run %t 2>&1 | FileCheck %s | ||
| // RUN: %env_memprof_opts=allocator_frees_and_returns_null_on_realloc_zero=true %run %t 2>&1 | FileCheck %s | ||
| // RUN: %env_memprof_opts=allocator_frees_and_returns_null_on_realloc_zero=false %run %t 2>&1 | FileCheck %s --check-prefix=NO-FREE | ||
|
|
||
| #include <stdio.h> | ||
| #include <stdlib.h> | ||
|
|
||
| int main() { | ||
| void *p = malloc(42); | ||
| p = realloc(p, 0); | ||
| if (p) { | ||
| // NO-FREE: Allocated something on realloc(p, 0) | ||
| fprintf(stderr, "Allocated something on realloc(p, 0)\n"); | ||
| } else { | ||
| // CHECK: realloc(p, 0) returned nullptr | ||
| fprintf(stderr, "realloc(p, 0) returned nullptr\n"); | ||
| } | ||
| free(p); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| // REQUIRES: memprof-64-bits | ||
| // Stress test dynamic TLS + dlopen + threads. | ||
| // | ||
| // RUN: %clangxx_memprof -x c -DSO_NAME=f0 %s -shared -o %t-f0.so -fPIC | ||
| // RUN: %clangxx_memprof -x c -DSO_NAME=f1 %s -shared -o %t-f1.so -fPIC | ||
| // RUN: %clangxx_memprof -x c -DSO_NAME=f2 %s -shared -o %t-f2.so -fPIC | ||
| // RUN: %clangxx_memprof %s -ldl -pthread -o %t | ||
| // RUN: %run %t 0 3 | ||
| // RUN: %run %t 2 3 | ||
| // RUN: %env_memprof_opts=verbosity=2 %run %t 10 2 2>&1 | FileCheck %s | ||
| // RUN: %env_memprof_opts=verbosity=2:intercept_tls_get_addr=1 %run %t 10 2 2>&1 | FileCheck %s | ||
| // RUN: %env_memprof_opts=verbosity=2:intercept_tls_get_addr=0 %run %t 10 2 2>&1 | FileCheck %s --check-prefix=CHECK0 | ||
| // CHECK: __tls_get_addr | ||
| // CHECK: Creating thread 0 | ||
| // CHECK: __tls_get_addr | ||
| // CHECK: Creating thread 1 | ||
| // CHECK: __tls_get_addr | ||
| // CHECK: Creating thread 2 | ||
| // CHECK: __tls_get_addr | ||
| // CHECK: Creating thread 3 | ||
| // CHECK: __tls_get_addr | ||
| // Make sure that TLS slots don't leak | ||
| // CHECK-NOT: num_live_dtls 5 | ||
| // | ||
| // CHECK0-NOT: __tls_get_addr | ||
| /* | ||
| cc=your-compiler | ||
| $cc stress_dtls.c -pthread -ldl | ||
| for((i=0;i<100;i++)); do | ||
| $cc -fPIC -shared -DSO_NAME=f$i -o a.out-f$i.so stress_dtls.c; | ||
| done | ||
| ./a.out 2 4 # <<<<<< 2 threads, 4 libs | ||
| ./a.out 3 50 # <<<<<< 3 threads, 50 libs | ||
| */ | ||
| #ifndef SO_NAME | ||
| #define _GNU_SOURCE | ||
| #include <assert.h> | ||
| #include <dlfcn.h> | ||
| #include <pthread.h> | ||
| #include <stdint.h> | ||
| #include <stdio.h> | ||
| #include <stdlib.h> | ||
|
|
||
| typedef void **(*f_t)(); | ||
|
|
||
| __thread int my_tls; | ||
|
|
||
| #define MAX_N_FUNCTIONS 1000 | ||
| f_t Functions[MAX_N_FUNCTIONS]; | ||
|
|
||
| void *PrintStuff(void *unused) { | ||
| uintptr_t stack; | ||
| // fprintf(stderr, "STACK: %p TLS: %p SELF: %p\n", &stack, &my_tls, | ||
| // (void *)pthread_self()); | ||
| int i; | ||
| for (i = 0; i < MAX_N_FUNCTIONS; i++) { | ||
| if (!Functions[i]) | ||
| break; | ||
| uintptr_t dtls = (uintptr_t)Functions[i](); | ||
| fprintf(stderr, " dtls[%03d]: %lx\n", i, dtls); | ||
| *(long *)dtls = 42; // check that this is writable. | ||
| } | ||
| return NULL; | ||
| } | ||
|
|
||
| int main(int argc, char *argv[]) { | ||
| int num_threads = 1; | ||
| int num_libs = 1; | ||
| if (argc >= 2) | ||
| num_threads = atoi(argv[1]); | ||
| if (argc >= 3) | ||
| num_libs = atoi(argv[2]); | ||
| assert(num_libs <= MAX_N_FUNCTIONS); | ||
|
|
||
| int lib; | ||
| for (lib = 0; lib < num_libs; lib++) { | ||
| char buf[4096]; | ||
| snprintf(buf, sizeof(buf), "%s-f%d.so", argv[0], lib); | ||
| void *handle = dlopen(buf, RTLD_LAZY); | ||
| if (!handle) { | ||
| fprintf(stderr, "%s\n", dlerror()); | ||
| exit(1); | ||
| } | ||
| snprintf(buf, sizeof(buf), "f%d", lib); | ||
| Functions[lib] = (f_t)dlsym(handle, buf); | ||
| if (!Functions[lib]) { | ||
| fprintf(stderr, "%s\n", dlerror()); | ||
| exit(1); | ||
| } | ||
| fprintf(stderr, "LIB[%03d] %s: %p\n", lib, buf, Functions[lib]); | ||
| PrintStuff(0); | ||
|
|
||
| int i; | ||
| for (i = 0; i < num_threads; i++) { | ||
| pthread_t t; | ||
| fprintf(stderr, "Creating thread %d\n", i); | ||
| pthread_create(&t, 0, PrintStuff, 0); | ||
| pthread_join(t, 0); | ||
| } | ||
| } | ||
| return 0; | ||
| } | ||
| #else // SO_NAME | ||
| #ifndef DTLS_SIZE | ||
| #define DTLS_SIZE (1 << 17) | ||
| #endif | ||
| __thread void *huge_thread_local_array[DTLS_SIZE]; | ||
| void **SO_NAME() { | ||
| return &huge_thread_local_array[0]; | ||
| } | ||
| #endif |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| // Check profile with a single malloc call and set of loads and stores. Ensures | ||
| // we get the same profile regardless of whether the memory is deallocated | ||
| // before exit. | ||
|
|
||
| // RUN: %clangxx_memprof -O0 %s -o %t | ||
| // RUN: %env_memprof_opts= %run %t 2>&1 | FileCheck %s | ||
|
|
||
| // RUN: %clangxx_memprof -DFREE -O0 %s -o %t | ||
| // RUN: %env_memprof_opts= %run %t 2>&1 | FileCheck %s | ||
|
|
||
| // This is actually: | ||
| // Memory allocation stack id = STACKID | ||
| // alloc_count 1, size (ave/min/max) 40.00 / 40 / 40 | ||
| // but we need to look for them in the same CHECK to get the correct STACKID. | ||
| // CHECK: Memory allocation stack id = [[STACKID:[0-9]+]]{{[[:space:]].*}}alloc_count 1, size (ave/min/max) 40.00 / 40 / 40 | ||
| // CHECK-NEXT: access_count (ave/min/max): 20.00 / 20 / 20 | ||
| // CHECK-NEXT: lifetime (ave/min/max): [[AVELIFETIME:[0-9]+]].00 / [[AVELIFETIME]] / [[AVELIFETIME]] | ||
| // CHECK-NEXT: num migrated: 0, num lifetime overlaps: 0, num same alloc cpu: 0, num same dealloc_cpu: 0 | ||
| // CHECK: Stack for id [[STACKID]]: | ||
| // CHECK-NEXT: #0 {{.*}} in malloc | ||
| // CHECK-NEXT: #1 {{.*}} in main {{.*}}:[[@LINE+6]] | ||
|
|
||
| #include <stdio.h> | ||
| #include <stdlib.h> | ||
|
|
||
| int main() { | ||
| int *p = (int *)malloc(10 * sizeof(int)); | ||
| for (int i = 0; i < 10; i++) | ||
| p[i] = i; | ||
| int j = 0; | ||
| for (int i = 0; i < 10; i++) | ||
| j += p[i]; | ||
| #ifdef FREE | ||
| free(p); | ||
| #endif | ||
|
|
||
| return 0; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| // Check profile with calls to memory intrinsics. | ||
|
|
||
| // RUN: %clangxx_memprof -O0 %s -o %t && %run %t 2>&1 | FileCheck %s | ||
|
|
||
| // This is actually: | ||
| // Memory allocation stack id = STACKIDP | ||
| // alloc_count 1, size (ave/min/max) 40.00 / 40 / 40 | ||
| // access_count (ave/min/max): 3.00 / 3 / 3 | ||
| // but we need to look for them in the same CHECK to get the correct STACKIDP. | ||
| // CHECK-DAG: Memory allocation stack id = [[STACKIDP:[0-9]+]]{{[[:space:]].*}} alloc_count 1, size (ave/min/max) 40.00 / 40 / 40{{[[:space:]].*}} access_count (ave/min/max): 3.00 / 3 / 3 | ||
| // | ||
| // This is actually: | ||
| // Memory allocation stack id = STACKIDQ | ||
| // alloc_count 1, size (ave/min/max) 20.00 / 20 / 20 | ||
| // access_count (ave/min/max): 2.00 / 2 / 2 | ||
| // but we need to look for them in the same CHECK to get the correct STACKIDQ. | ||
| // CHECK-DAG: Memory allocation stack id = [[STACKIDQ:[0-9]+]]{{[[:space:]].*}} alloc_count 1, size (ave/min/max) 20.00 / 20 / 20{{[[:space:]].*}} access_count (ave/min/max): 2.00 / 2 / 2 | ||
|
|
||
| #include <stdio.h> | ||
| #include <stdlib.h> | ||
| #include <string.h> | ||
|
|
||
| int main() { | ||
| // This is actually: | ||
| // Stack for id STACKIDP: | ||
| // #0 {{.*}} in operator new | ||
| // #1 {{.*}} in main {{.*}}:@LINE+1 | ||
| // but we need to look for them in the same CHECK-DAG. | ||
| // CHECK-DAG: Stack for id [[STACKIDP]]:{{[[:space:]].*}} #0 {{.*}} in operator new{{.*[[:space:]].*}} #1 {{.*}} in main {{.*}}:[[@LINE+1]] | ||
| int *p = new int[10]; | ||
|
|
||
| // This is actually: | ||
| // Stack for id STACKIDQ: | ||
| // #0 {{.*}} in operator new | ||
| // #1 {{.*}} in main {{.*}}:@LINE+1 | ||
| // but we need to look for them in the same CHECK-DAG. | ||
| // CHECK-DAG: Stack for id [[STACKIDQ]]:{{[[:space:]].*}} #0 {{.*}} in operator new{{.*[[:space:]].*}} #1 {{.*}} in main {{.*}}:[[@LINE+1]] | ||
| int *q = new int[5]; | ||
|
|
||
| memset(p, 1, 10); | ||
| memcpy(q, p, 5); | ||
| int x = memcmp(p, q, 5); | ||
|
|
||
| delete p; | ||
| delete q; | ||
|
|
||
| return x; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| // Check profile with a single new call and set of loads and stores. Ensures | ||
| // we get the same profile regardless of whether the memory is deallocated | ||
| // before exit. | ||
|
|
||
| // RUN: %clangxx_memprof -O0 %s -o %t | ||
| // RUN: %env_memprof_opts= %run %t 2>&1 | FileCheck %s | ||
|
|
||
| // RUN: %clangxx_memprof -DFREE -O0 %s -o %t | ||
| // RUN: %env_memprof_opts= %run %t 2>&1 | FileCheck %s | ||
|
|
||
| // Try again with callbacks instead of inline sequences | ||
| // RUN: %clangxx_memprof -mllvm -memprof-use-callbacks -O0 %s -o %t | ||
| // RUN: %env_memprof_opts= %run %t 2>&1 | FileCheck %s | ||
|
|
||
| // This is actually: | ||
| // Memory allocation stack id = STACKID | ||
| // alloc_count 1, size (ave/min/max) 40.00 / 40 / 40 | ||
| // but we need to look for them in the same CHECK to get the correct STACKID. | ||
| // CHECK: Memory allocation stack id = [[STACKID:[0-9]+]]{{[[:space:]].*}}alloc_count 1, size (ave/min/max) 40.00 / 40 / 40 | ||
| // CHECK-NEXT: access_count (ave/min/max): 20.00 / 20 / 20 | ||
| // CHECK-NEXT: lifetime (ave/min/max): [[AVELIFETIME:[0-9]+]].00 / [[AVELIFETIME]] / [[AVELIFETIME]] | ||
| // CHECK-NEXT: num migrated: 0, num lifetime overlaps: 0, num same alloc cpu: 0, num same dealloc_cpu: 0 | ||
| // CHECK: Stack for id [[STACKID]]: | ||
| // CHECK-NEXT: #0 {{.*}} in operator new | ||
| // CHECK-NEXT: #1 {{.*}} in main {{.*}}:[[@LINE+6]] | ||
|
|
||
| #include <stdio.h> | ||
| #include <stdlib.h> | ||
|
|
||
| int main() { | ||
| int *p = new int[10]; | ||
| for (int i = 0; i < 10; i++) | ||
| p[i] = i; | ||
| int j = 0; | ||
| for (int i = 0; i < 10; i++) | ||
| j += p[i]; | ||
| #ifdef FREE | ||
| delete p; | ||
| #endif | ||
|
|
||
| return 0; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| // Check terse format profile with a single malloc call and set of loads and | ||
| // stores. Ensures we get the same profile regardless of whether the memory is | ||
| // deallocated before exit. | ||
|
|
||
| // RUN: %clangxx_memprof -O0 %s -o %t | ||
| // RUN: %env_memprof_opts=print_terse=1 %run %t 2>&1 | FileCheck %s | ||
|
|
||
| // RUN: %clangxx_memprof -DFREE -O0 %s -o %t | ||
| // RUN: %env_memprof_opts=print_terse=1 %run %t 2>&1 | FileCheck %s | ||
|
|
||
| // CHECK: MIB:[[STACKID:[0-9]+]]/1/40.00/40/40/20.00/20/20/[[AVELIFETIME:[0-9]+]].00/[[AVELIFETIME]]/[[AVELIFETIME]]/0/0/0/0 | ||
| // CHECK: Stack for id [[STACKID]]: | ||
| // CHECK-NEXT: #0 {{.*}} in operator new | ||
| // CHECK-NEXT: #1 {{.*}} in main {{.*}}:[[@LINE+6]] | ||
|
|
||
| #include <stdio.h> | ||
| #include <stdlib.h> | ||
|
|
||
| int main() { | ||
| int *p = new int[10]; | ||
| for (int i = 0; i < 10; i++) | ||
| p[i] = i; | ||
| int j = 0; | ||
| for (int i = 0; i < 10; i++) | ||
| j += p[i]; | ||
| #ifdef FREE | ||
| delete p; | ||
| #endif | ||
|
|
||
| return 0; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| // RUN: %clangxx_memprof -O0 %s -o %t && %run %t 2>&1 | FileCheck %s | ||
|
|
||
| // This is actually: | ||
| // Memory allocation stack id = STACKID | ||
| // alloc_count 1, size (ave/min/max) 128.00 / 128 / 128 | ||
| // but we need to look for them in the same CHECK to get the correct STACKID. | ||
| // CHECK: Memory allocation stack id = [[STACKID:[0-9]+]]{{[[:space:]].*}}alloc_count 1, size (ave/min/max) 128.00 / 128 / 128 | ||
| // CHECK-NEXT: access_count (ave/min/max): 7.00 / 7 / 7 | ||
|
|
||
| #include <sanitizer/memprof_interface.h> | ||
|
|
||
| #include <stdlib.h> | ||
| #include <string.h> | ||
| int main(int argc, char **argv) { | ||
| // CHECK: Stack for id [[STACKID]]: | ||
| // CHECK-NEXT: #0 {{.*}} in operator new[](unsigned long) | ||
| // CHECK-NEXT: #1 {{.*}} in main {{.*}}:[[@LINE+1]] | ||
| char *x = new char[128]; | ||
| memset(x, 0xab, 128); | ||
| __sanitizer_unaligned_load16(x + 15); | ||
| __sanitizer_unaligned_load32(x + 15); | ||
| __sanitizer_unaligned_load64(x + 15); | ||
|
|
||
| __sanitizer_unaligned_store16(x + 15, 0); | ||
| __sanitizer_unaligned_store32(x + 15, 0); | ||
| __sanitizer_unaligned_store64(x + 15, 0); | ||
|
|
||
| delete[] x; | ||
| return 0; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| # -*- Python -*- | ||
|
|
||
| import os | ||
| import platform | ||
| import re | ||
|
|
||
| import lit.formats | ||
|
|
||
| # Get shlex.quote if available (added in 3.3), and fall back to pipes.quote if | ||
| # it's not available. | ||
| try: | ||
| import shlex | ||
| sh_quote = shlex.quote | ||
| except: | ||
| import pipes | ||
| sh_quote = pipes.quote | ||
|
|
||
| def get_required_attr(config, attr_name): | ||
| attr_value = getattr(config, attr_name, None) | ||
| if attr_value == None: | ||
| lit_config.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.py " % attr_name) | ||
| return attr_value | ||
|
|
||
| # Setup config name. | ||
| config.name = 'MemProfiler' + config.name_suffix | ||
|
|
||
| # Platform-specific default MEMPROF_OPTIONS for lit tests. | ||
| default_memprof_opts = list(config.default_sanitizer_opts) | ||
|
|
||
| default_memprof_opts_str = ':'.join(default_memprof_opts) | ||
| if default_memprof_opts_str: | ||
| config.environment['MEMPROF_OPTIONS'] = default_memprof_opts_str | ||
| config.substitutions.append(('%env_memprof_opts=', | ||
| 'env MEMPROF_OPTIONS=' + default_memprof_opts_str)) | ||
|
|
||
| # Setup source root. | ||
| config.test_source_root = os.path.dirname(__file__) | ||
|
|
||
| libdl_flag = '-ldl' | ||
|
|
||
| # Setup default compiler flags used with -fmemory-profile option. | ||
| # FIXME: Review the set of required flags and check if it can be reduced. | ||
| target_cflags = [get_required_attr(config, 'target_cflags')] | ||
| target_cxxflags = config.cxx_mode_flags + target_cflags | ||
| clang_memprof_static_cflags = (['-fmemory-profile', | ||
| '-mno-omit-leaf-frame-pointer', | ||
| '-fno-omit-frame-pointer', | ||
| '-fno-optimize-sibling-calls'] + | ||
| config.debug_info_flags + target_cflags) | ||
| clang_memprof_static_cxxflags = config.cxx_mode_flags + clang_memprof_static_cflags | ||
|
|
||
| memprof_dynamic_flags = [] | ||
| if config.memprof_dynamic: | ||
| memprof_dynamic_flags = ['-shared-libsan'] | ||
| config.available_features.add('memprof-dynamic-runtime') | ||
| else: | ||
| config.available_features.add('memprof-static-runtime') | ||
| clang_memprof_cflags = clang_memprof_static_cflags + memprof_dynamic_flags | ||
| clang_memprof_cxxflags = clang_memprof_static_cxxflags + memprof_dynamic_flags | ||
|
|
||
| def build_invocation(compile_flags): | ||
| return ' ' + ' '.join([config.clang] + compile_flags) + ' ' | ||
|
|
||
| config.substitutions.append( ("%clang ", build_invocation(target_cflags)) ) | ||
| config.substitutions.append( ("%clangxx ", build_invocation(target_cxxflags)) ) | ||
| config.substitutions.append( ("%clang_memprof ", build_invocation(clang_memprof_cflags)) ) | ||
| config.substitutions.append( ("%clangxx_memprof ", build_invocation(clang_memprof_cxxflags)) ) | ||
| if config.memprof_dynamic: | ||
| shared_libmemprof_path = os.path.join(config.compiler_rt_libdir, 'libclang_rt.memprof{}.so'.format(config.target_suffix)) | ||
| config.substitutions.append( ("%shared_libmemprof", shared_libmemprof_path) ) | ||
| config.substitutions.append( ("%clang_memprof_static ", build_invocation(clang_memprof_static_cflags)) ) | ||
| config.substitutions.append( ("%clangxx_memprof_static ", build_invocation(clang_memprof_static_cxxflags)) ) | ||
|
|
||
| # Some tests uses C++11 features such as lambdas and need to pass -std=c++11. | ||
| config.substitutions.append(("%stdcxx11 ", '-std=c++11 ')) | ||
|
|
||
| config.substitutions.append( ("%libdl", libdl_flag) ) | ||
|
|
||
| config.available_features.add('memprof-' + config.bits + '-bits') | ||
|
|
||
| config.available_features.add('fast-unwinder-works') | ||
|
|
||
| # Set LD_LIBRARY_PATH to pick dynamic runtime up properly. | ||
| new_ld_library_path = os.path.pathsep.join( | ||
| (config.compiler_rt_libdir, config.environment.get('LD_LIBRARY_PATH', ''))) | ||
| config.environment['LD_LIBRARY_PATH'] = new_ld_library_path | ||
|
|
||
| # Default test suffixes. | ||
| config.suffixes = ['.c', '.cpp'] | ||
|
|
||
| config.substitutions.append(('%fPIC', '-fPIC')) | ||
| config.substitutions.append(('%fPIE', '-fPIE')) | ||
| config.substitutions.append(('%pie', '-pie')) | ||
|
|
||
| # Only run the tests on supported OSs. | ||
| if config.host_os not in ['Linux']: | ||
| config.unsupported = True | ||
|
|
||
| if not config.parallelism_group: | ||
| config.parallelism_group = 'shadow-memory' |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| @LIT_SITE_CFG_IN_HEADER@ | ||
|
|
||
| # Tool-specific config options. | ||
| config.name_suffix = "@MEMPROF_TEST_CONFIG_SUFFIX@" | ||
| config.target_cflags = "@MEMPROF_TEST_TARGET_CFLAGS@" | ||
| config.clang = "@MEMPROF_TEST_TARGET_CC@" | ||
| config.bits = "@MEMPROF_TEST_BITS@" | ||
| config.memprof_dynamic = @MEMPROF_TEST_DYNAMIC@ | ||
| config.target_arch = "@MEMPROF_TEST_TARGET_ARCH@" | ||
|
|
||
| # Load common config for all compiler-rt lit tests. | ||
| lit_config.load_config(config, "@COMPILER_RT_BINARY_DIR@/test/lit.common.configured") | ||
|
|
||
| # Load tool-specific config that would do the real work. | ||
| lit_config.load_config(config, "@MEMPROF_LIT_SOURCE_DIR@/lit.cfg.py") |