blob: 247bcf43b5181a81ede7416db610404ce3023b5e [file] [log] [blame]
/*
* Copyright 2011 The Native Client Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include <pthread.h>
#include <semaphore.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
static void CheckSuccess(int err, const char *filename, int lineno,
const char *expr) {
if (err != 0) {
printf("pthread/sem function failed with errno %i at %s:%i: %s\n",
err, filename, lineno, expr);
exit(1);
}
}
static void Check(int cond, const char *filename, int lineno,
const char *expr) {
if (!cond) {
printf("condition failed at %s:%i: %s\n",
filename, lineno, expr);
exit(1);
}
}
#define CHECK_OK(expr) (CheckSuccess((expr), __FILE__, __LINE__, #expr))
#define CHECK(expr) (Check((expr), __FILE__, __LINE__, #expr))
uintptr_t g_thread2_stack_start = 0;
uintptr_t g_thread2_stack_end = 0;
#define THREAD1_STACK_SIZE (128*1024)
#define THREAD2_STACK_SIZE (1024*1024)
#define TEST_ALLOCATION_SIZE (64*1024)
void* thread1_func(void* arg)
{
pthread_exit(NULL);
return NULL; /* Quiet compiler error */
}
struct thread2_arg {
sem_t* thread2_started;
sem_t* main_thread_done;
};
void* thread2_func(void* arg)
{
int dummy = 0;
const unsigned int kStackAlignment = 32;
struct thread2_arg *args = (struct thread2_arg*)arg;
/* Get a rough idea of the stack extents, may be off
* by a few bytes but it's unimportant as long as we
* don't over estimate the end and the begining isn't
* under estimated by more than TEST_ALLOCATION_SIZE.
*/
g_thread2_stack_end = (uintptr_t)&dummy;
g_thread2_stack_end &= ~kStackAlignment;
/* If we got the same stack as thread1, it's possible that
* the stack for thread2 won't fit between 0x0 and the end.
*/
CHECK(g_thread2_stack_end > THREAD2_STACK_SIZE);
g_thread2_stack_start = g_thread2_stack_end - THREAD2_STACK_SIZE;
CHECK_OK(sem_post(args->thread2_started));
/* Keep thread alive while we check an allocation in the main thread. */
CHECK_OK(sem_wait(args->main_thread_done));
pthread_exit(NULL);
return NULL; /* Quiet compiler error */
}
/* Make sure thread stacks are allocated correctly, not inherited from
* previous threads when they don't fit. This test creates a thread with
* a small stack, waits for its exit, then creates a thread with a large
* stack. Then it allocates some memory to make sure a proper stack was
* allocated.
*/
void TestThreadStackAllocation(void) {
pthread_t thread1, thread2;
pthread_attr_t attr1, attr2;
sem_t thread2_started, main_thread_done;
struct thread2_arg arg;
CHECK_OK(pthread_attr_init(&attr1));
CHECK_OK(pthread_attr_init(&attr2));
CHECK_OK(pthread_attr_setstacksize(&attr1, THREAD1_STACK_SIZE));
CHECK_OK(pthread_attr_setstacksize(&attr2, THREAD2_STACK_SIZE));
CHECK_OK(pthread_create(&thread1, &attr1, thread1_func, NULL));
CHECK_OK(pthread_join(thread1, NULL));
CHECK_OK(sem_init(&thread2_started, 0, 0));
CHECK_OK(sem_init(&main_thread_done, 0, 0));
arg.thread2_started = &thread2_started;
arg.main_thread_done = &main_thread_done;
CHECK_OK(pthread_create(&thread2, &attr2, thread2_func, &arg));
/* Wait for thread to spin up. */
CHECK_OK(sem_wait(&thread2_started));
uintptr_t test_alloc = (uintptr_t)malloc(TEST_ALLOCATION_SIZE);
/* Check to make sure test_alloc doesn't lie in the stack of thread2. */
CHECK(test_alloc < g_thread2_stack_start - TEST_ALLOCATION_SIZE ||
test_alloc > g_thread2_stack_end);
free((void*)test_alloc);
/* Signal thread2 to exit. */
CHECK_OK(sem_post(&main_thread_done));
CHECK_OK(pthread_join(thread2, NULL));
CHECK_OK(sem_destroy(&thread2_started));
CHECK_OK(sem_destroy(&main_thread_done));
}
int main(int argc, char *argv[]) {
/* Please don't add tests before TestThreadStackAllocation,
* it could yield a false negative if the heap is fragmented.
*/
TestThreadStackAllocation();
return 0;
}