// Copyright (C) 2013 The Android Open Source Project // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // 3. Neither the name of the project nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF // SUCH DAMAGE. // A test used to check that __cxa_get_globals() does not use malloc. // This will do the following: // // - Lazily load libtest_malloc_lockup.so, which includes a copy of // GAbi++ linked with malloc() / free() functions that exit() with // an error if called. // // - Create a large number of concurrent threads, and have each one // call the library's 'get_globals' function, which returns the // result of __cxa_get_globals() linked against the special mallocs, // then store the value in a global array. // // - Tell all the threads to stop, wait for them to complete. // // - Look at the values stored in the global arrays. They should not be NULL // (to indicate succesful allocation), and all different (each one should // correspond to a thread-specific instance of __cxa_eh_globals). // // - Unload the library. // #include <dlfcn.h> #include <errno.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <string.h> typedef void* (*get_globals_fn)(); static get_globals_fn g_get_globals; // Number of threads to create. Must be > 4096 to really check slab allocation. static const size_t kMaxThreads = 5000; static pthread_t g_threads[kMaxThreads]; static void* g_thread_objects[kMaxThreads]; static pthread_mutex_t g_lock = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t g_cond_exit = PTHREAD_COND_INITIALIZER; static pthread_cond_t g_cond_counter = PTHREAD_COND_INITIALIZER; static unsigned g_thread_count = 0; static bool g_can_exit = false; // Thread routine, just call 'get_globals' and store the result in our global // array, then wait for an event from the main thread. This guarantees that // no thread exits before another one starts, and thus that allocation slots // are not reused. static void* my_thread(void* param) { // Get thread-specific object pointer, store it in global array. int id = (int)(intptr_t)param; g_thread_objects[id] = (*g_get_globals)(); // Increment global thread counter and tell the main thread about this. pthread_mutex_lock(&g_lock); g_thread_count += 1; pthread_cond_signal(&g_cond_counter); // The thread object will be automatically released/recycled when the thread // exits. Wait here until signaled by the main thread to avoid this. while (!g_can_exit) pthread_cond_wait(&g_cond_exit, &g_lock); pthread_mutex_unlock(&g_lock); return NULL; } int main(void) { // Load the library. void* lib = dlopen("libtest_malloc_lockup.so", RTLD_NOW); if (!lib) { fprintf(stderr, "ERROR: Can't find library: %s\n", strerror(errno)); return 1; } // Extract 'get_globals' function address. g_get_globals = reinterpret_cast<get_globals_fn>(dlsym(lib, "get_globals")); if (!g_get_globals) { fprintf(stderr, "ERROR: Could not find 'get_globals' function: %s\n", dlerror()); dlclose(lib); return 1; } // Use a smaller stack per thread to be able to create lots of them. pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setstacksize(&attr, 16384); // Start as many threads as needed. printf("Creating %d threads\n", kMaxThreads); for (size_t n = 0; n < kMaxThreads; ++n) { int ret = pthread_create(&g_threads[n], &attr, my_thread, (void*)n); if (ret != 0) { fprintf(stderr, "ERROR: Thread #%d creation error: %s\n", n + 1, strerror(errno)); return 2; } } // Wait until they all ran, then tell them to exit. printf("Waiting for all threads to run\n"); pthread_mutex_lock(&g_lock); while (g_thread_count < kMaxThreads) pthread_cond_wait(&g_cond_counter, &g_lock); printf("Waking up threads\n"); g_can_exit = true; pthread_cond_broadcast(&g_cond_exit); pthread_mutex_unlock(&g_lock); // Wait for them to complete. printf("Waiting for all threads to complete\n"); for (size_t n = 0; n < kMaxThreads; ++n) { void* dummy; pthread_join(g_threads[n], &dummy); } // Verify that the thread objects are all non-NULL and different. printf("Checking results\n"); size_t failures = 0; const size_t kMaxFailures = 16; for (size_t n = 0; n < kMaxThreads; ++n) { void* obj = g_thread_objects[n]; if (obj == NULL) { if (++failures < kMaxFailures) printf("Thread %d got a NULL object!\n", n + 1); } else { for (size_t m = n + 1; m < kMaxThreads; ++m) { if (g_thread_objects[m] == obj) { if (++failures < kMaxFailures) printf("Thread %d has same object as thread %d (%p)\n", n + 1, m + 1, obj); } } } } // We're done. dlclose(lib); if (failures > 0) { fprintf(stderr, "%d failures detected!\n", failures); return 1; } printf("All OK!\n"); return 0; }