#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <err.h>
#if DARWINTEST
#include <darwintest.h>
T_GLOBAL_META(T_META_RUN_CONCURRENTLY(true));
#endif
int rseed;
int min_bytes;
int max_bytes;
long long max_mem;
long long mem_allocated;
int free_pct;
int max_malloc_calls;
int malloc_calls_made;
int malloc_bufs;
int time_limit;
int debug_dump;
#define MAX_MALLOC_INFO 10000000
struct malloc_info {
void *buf_ptr;
int buf_size;
int set_val;
int this_buffer_freed;
} minfo_array[MAX_MALLOC_INFO];
#define D100 (1 + (rand() % 100))
int signal_happened = 0;
void
sig_handler(int signo)
{
signal_happened = signo;
return;
}
void
trap_signals()
{
int signum;
for (signum = 1; signum < NSIG; signum++) {
if (signal(signum, sig_handler) == SIG_ERR && (signum != SIGKILL && signum != SIGSTOP)) {
#if DARWINTEST
T_FAIL("Could not trap signal %d (OK)\n", signum);
#else
printf("INFO: Could not trap signal %d (OK)\n", signum);
#endif
};
}
}
void
usage()
{
printf("\nusage: malloc_stress [options...]\n");
printf("Default: Allocate up to %d buffers, with a %d percent free() chance.\n", MAX_MALLOC_INFO, free_pct);
printf(" Buffer sizes range from %d to %d bytes.\n", min_bytes, max_bytes);
printf("\nOptions:\n");
printf(" -min #bytes Minimum buffer size to allocate (at least 1).\n");
printf(" -max #bytes Maximum buffer size (up to 2gb allowed).\n");
printf(" -mem #bytes Stop when total allocations surpasses this.\n");
printf(" -free # Percent chance to free a buffer.\n");
printf(" -calls # Maximum allocations to do, then stop.\n");
printf(" -seed # Set the random seed to this number.\n");
printf(" -dbg Produce some debugging outputs.\n");
exit(99);
}
void
summarize()
{
int mx;
printf("INFO: %d total malloc() calls made.\n", malloc_calls_made);
printf("INFO: %d total free() calls made during testing.\n", malloc_calls_made - malloc_bufs);
printf("INFO: Total memory allocated: %lld bytes\n", mem_allocated);
if (debug_dump) {
for (mx = 0; mx < malloc_calls_made; mx++) {
printf("Buffer index %d: Address = 0x%llx, Size=%d, Fill=%d ", mx, (unsigned long long)(minfo_array[mx].buf_ptr),
minfo_array[mx].buf_size, minfo_array[mx].set_val);
if (minfo_array[mx].this_buffer_freed) {
printf("[freed]");
}
printf("\n");
}
}
printf("INFO: Random seed value = %d\n", rseed);
return;
}
void
cleanup()
{
int mx;
#ifndef DARWINTEST
printf("Cleanup: Started.\n");
printf("Cleanup: Every allocated buffer should be free-able.\n");
#endif
for (mx = 0; mx < malloc_calls_made; mx++) {
if (!(minfo_array[mx].this_buffer_freed)) {
free(minfo_array[mx].buf_ptr);
if (signal_happened) {
#if DARWINTEST
T_FAIL("Signal %d occurred during free(0x%llx)", signal_happened, (unsigned long long)(minfo_array[mx].buf_ptr));
#else
printf("FAIL: Signal %d occurred during free(0x%llx)\n", signal_happened,
(unsigned long long)(minfo_array[mx].buf_ptr));
exit(1);
#endif
}
}
}
return;
}
int
check_overlap(void *p1, int len1, void *p2, int len2)
{
unsigned long long bob1, eob1, bob2, eob2;
bob1 = (unsigned long long)p1;
eob1 = bob1 + (unsigned long long)len1 - 1LL;
bob2 = (unsigned long long)p2;
eob2 = bob2 + (unsigned long long)len2 - 1LL;
if ((bob1 >= bob2 && bob1 <= eob2) || (eob1 >= bob2 && eob1 <= eob2) || (bob2 >= bob1 && bob2 <= eob1) ||
(eob2 >= bob1 && eob2 <= eob1)) {
#ifdef DARWINTEST
T_FAIL("Buffers overlap: Buffer 1 (0x%llx, %d bytes), Buffer 2 (0x%llx, %d bytes)", bob1, len1, bob2, len2);
#else
printf("FAIL: Buffers overlap: Buffer 1 (0x%llx, %d bytes), Buffer 2 (0x%llx, %d bytes)\n", bob1, len1, bob2, len2);
#endif
return 1;
}
return 0;
}
void do_test(void);
#ifndef DARWINTEST
int
main(int argc, char *argv[])
{
int argx;
malloc_calls_made = malloc_bufs = 0;
mem_allocated = 0;
rseed = (int)time(0);
min_bytes = 1;
max_bytes = (1024 * 1024);
max_mem = 0;
free_pct = 10;
max_malloc_calls = MAX_MALLOC_INFO;
time_limit = 0;
debug_dump = 0;
for (argx = 1; argx < argc; argx++) {
if (strcmp("-min", argv[argx]) == 0) {
min_bytes = atoi(argv[++argx]);
} else if (strcmp("-max", argv[argx]) == 0) {
max_bytes = atoi(argv[++argx]);
} else if (strcmp("-mem", argv[argx]) == 0) {
max_mem = atoll(argv[++argx]);
} else if (strcmp("-seed", argv[argx]) == 0) {
rseed = atoi(argv[++argx]);
} else if (strcmp("-free", argv[argx]) == 0) {
free_pct = atoi(argv[++argx]);
} else if (strcmp("-calls", argv[argx]) == 0) {
max_malloc_calls = atoi(argv[++argx]);
} else if (strcmp("-time", argv[argx]) == 0) {
time_limit = atoi(argv[++argx]);
} else if (strcmp("-dbg", argv[argx]) == 0) {
debug_dump = 1;
} else if (strcmp("-h", argv[argx]) == 0) {
usage();
} else {
printf("Unknown argument: '%s'\n", argv[argx]);
usage();
}
}
if (min_bytes < 1) {
printf("Minimum bytes (-min %d) must be at least 1.\n", min_bytes);
usage();
}
if (max_bytes < 1) {
printf("Maximum bytes (-max %d) must be at least 1.\n", max_bytes);
usage();
}
if (min_bytes > max_bytes) {
printf("Minimum bytes (-min %d) must not exceed max bytes (-max %d)\n", min_bytes, max_bytes);
usage();
}
if (free_pct < 0 || free_pct > 100) {
printf("Percentage to free (-free %d) must be between 0 and 100.\n", free_pct);
usage();
}
if (max_malloc_calls < 1) {
printf("Maximum malloc calls (-calls %d) must be at least 1.\n", max_malloc_calls);
usage();
}
if (max_malloc_calls > MAX_MALLOC_INFO) {
printf("This program has been compiled for %d max allocations.\n", MAX_MALLOC_INFO);
printf("To support more, re-compile malloc_stress.c with a larger MAX_MALLOC_INFO\n");
usage();
}
if (time_limit < 0) {
printf("The maximum execution time specified (%d seconds) must be 0 or positive.\n", time_limit);
usage();
}
if (max_malloc_calls) {
printf("Will call malloc() up to %d times.\n", max_malloc_calls);
} else {
printf("Will call malloc() repeatedly until it returns NULL.\n");
}
if (min_bytes == max_bytes) {
printf("Will allocate buffers all of size %d bytes.\n", min_bytes);
} else {
printf("Will allocate buffers between %d and %d bytes.\n", min_bytes, max_bytes);
}
if (free_pct) {
printf("Will free() a malloc'ed buffer %d%% of the time.\n", free_pct);
} else {
printf("free() will NOT be called between malloc's.\n");
}
if (time_limit > 0) {
printf("Will stop after %d elapsed seconds.\n", time_limit);
}
srand(rseed);
printf("Random seed value = %d\n", rseed);
fflush(stdout);
do_test();
return 0;
}
#endif
void
do_test(void)
{
time_t start_time = time(0);
trap_signals();
for (;;) {
int buf_size;
int bx;
char *malloc_buf;
int set_buf_val;
int save_errno;
if (time_limit > 0 && (time(0) - start_time >= time_limit)) {
#if DARWINTEST
cleanup();
T_PASS("Ran until time limit without incident.");
return;
#else
printf("INFO: Execution time limit has been reached.\n");
summarize();
cleanup();
printf("PASS\n");
exit(0);
#endif
}
if (malloc_bufs > 0 && (D100 < free_pct)) {
int random_buf;
for (;;) {
random_buf = rand() % malloc_calls_made;
if (!(minfo_array[random_buf].this_buffer_freed)) {
free(minfo_array[random_buf].buf_ptr);
if (signal_happened) {
#if DARWINTEST
T_FAIL("Signal %d caught during free() of buffer 0x%llx\n", signal_happened,
(unsigned long long)(minfo_array[random_buf].buf_ptr));
return;
#else
summarize();
printf("FAIL: Signal %d caught during free() of buffer 0x%llx\n", signal_happened,
(unsigned long long)(minfo_array[random_buf].buf_ptr));
exit(1);
#endif
}
minfo_array[random_buf].this_buffer_freed = 1;
malloc_bufs--;
if (debug_dump) {
printf("INFO: Freed buffer index %d, (%d bytes) at address 0x%llx\n", random_buf,
minfo_array[random_buf].buf_size, (unsigned long long)(minfo_array[random_buf].buf_ptr));
}
break;
}
}
continue;
}
buf_size = min_bytes + (rand() % (max_bytes - min_bytes + 1));
errno = 0;
malloc_buf = malloc(buf_size);
save_errno = errno;
if (signal_happened) {
#if DARWINTEST
T_FAIL("Signal %d caught during malloc(%d).", signal_happened, buf_size);
return;
#else
summarize();
printf("FAIL: Signal %d caught during malloc()!\n", signal_happened);
printf("Buffer size being allocated was %d bytes.\n", buf_size);
exit(1);
#endif
}
if (debug_dump) {
printf("INFO: Allocated buffer (%d bytes) at address 0x%llx\n", buf_size, (unsigned long long)malloc_buf);
}
if (malloc_buf == NULL) {
#if DARWINTEST
T_ASSERT_EQ_INT(save_errno, ENOMEM, "malloc failed, but with errno = %d instead of ENOMEM.", save_errno);
cleanup();
T_PASS("Ran out of memory without incident.");
return;
#else
if (save_errno != ENOMEM) {
printf("FAIL: malloc failed, but with errno = %d instead of ENOMEM.\n", save_errno);
summarize();
exit(1);
}
summarize();
cleanup();
printf("PASS\n");
exit(0);
#endif
}
for (bx = 0; bx < malloc_calls_made; bx++) {
if (minfo_array[bx].this_buffer_freed) {
continue;
}
if (check_overlap(malloc_buf, buf_size, minfo_array[bx].buf_ptr, minfo_array[bx].buf_size)) {
#if DARWINTEST
return;
#else
summarize();
printf("FAIL: Allocated buffer overlaps with buffer index %d\n", bx);
exit(1);
#endif
}
}
for (bx = 0; bx < buf_size; bx++) {
char byte;
*((volatile char *)&byte) = *((volatile char *)malloc_buf + bx);
if (signal_happened) {
#if DARWINTEST
T_FAIL("Signal %d caught reading buffer!", signal_happened);
return;
#else
summarize();
printf("FAIL: Signal %d caught reading buffer!\n", signal_happened);
exit(1);
#endif
}
}
if (sizeof(short) <= buf_size) {
*((short *)malloc_buf) = 1;
}
if (sizeof(int) <= buf_size) {
*((int *)malloc_buf) = 2;
}
if (sizeof(long) <= buf_size) {
*((long *)malloc_buf) = 3L;
}
if (sizeof(long long) <= buf_size) {
*((long long *)malloc_buf) = 4LL;
}
if (sizeof(float) <= buf_size) {
*((float *)malloc_buf) = 5.0;
}
if (sizeof(double) <= buf_size) {
*((double *)malloc_buf) = 6.1;
}
if (sizeof(long double) <= buf_size) {
*((long double *)malloc_buf) = 7.2;
}
if (signal_happened) {
#if DARWINTEST
T_FAIL("Signal %d occurred storing numeric types at address 0x%llx (%d bytes)", signal_happened,
(unsigned long long)malloc_buf, buf_size);
return;
#else
printf("\nFAIL: Signal %d occurred storing numeric types at address 0x%llx (%d bytes)\n", signal_happened,
(unsigned long long)malloc_buf, buf_size);
summarize();
exit(1);
#endif
}
set_buf_val = rand() & 0xFF;
memset(malloc_buf, set_buf_val, buf_size);
if (signal_happened) {
#if DARWINTEST
T_FAIL("Signal %d caught initializing buffer to byte value %d!", signal_happened, set_buf_val);
return;
#else
summarize();
printf("FAIL: Signal %d caught initializing buffer to byte value %d!\n", signal_happened, set_buf_val);
exit(1);
#endif
}
minfo_array[malloc_calls_made].buf_ptr = malloc_buf;
minfo_array[malloc_calls_made].buf_size = buf_size;
minfo_array[malloc_calls_made].set_val = set_buf_val;
minfo_array[malloc_calls_made].this_buffer_freed = 0;
malloc_calls_made++;
malloc_bufs++;
mem_allocated += buf_size;
if (malloc_calls_made >= max_malloc_calls) {
#if DARWINTEST
cleanup();
T_PASS("Maximum malloc calls reached: %d", malloc_calls_made);
return;
#else
printf("Maximum malloc calls reached: %d\n", malloc_calls_made);
summarize();
cleanup();
printf("PASS\n");
exit(0);
#endif
}
if (max_mem > 0 && mem_allocated >= max_mem) {
#if DARWINTEST
cleanup();
T_PASS("Maximum memory allocation reached: %lld", mem_allocated);
return;
#else
printf("Maximum memory allocation reached: %lld\n", mem_allocated);
cleanup();
printf("PASS\n");
exit(0);
#endif
}
for (bx = 0; bx < malloc_calls_made; bx++) {
int cx;
unsigned char *bptr = minfo_array[bx].buf_ptr;
int expected_val = minfo_array[bx].set_val;
if (minfo_array[bx].this_buffer_freed) {
continue;
}
for (cx = 0; cx < minfo_array[bx].buf_size; cx++) {
if (bptr[cx] != expected_val) {
#if DARWINTEST
T_FAIL("Allocated buffer [%d] appears to have been stepped on.", bx);
return;
#else
summarize();
printf("FAIL: Allocated buffer [%d] appears to have been stepped on.\n", bx);
printf("Buffer address: 0x%llx, bad byte index %d\n", (unsigned long long)bptr, cx);
printf("Expected byte value %d, but found %d\n", expected_val, (int)(bptr[cx]));
exit(1);
#endif
}
}
if (signal_happened) {
#if DARWINTEST
T_FAIL("Signal %d caught validating buffer contents.\n", signal_happened);
return;
#else
summarize();
printf("FAIL: Signal %d caught validating buffer contents.\n", signal_happened);
exit(1);
#endif
}
}
}
}
#if DARWINTEST
static void
setup_and_test(void)
{
rseed = 42;
min_bytes = 1;
max_bytes = (1024 * 1024);
max_mem = (512 * 1024 * 1024);
free_pct = 10;
max_malloc_calls = MAX_MALLOC_INFO;
time_limit = 15;
debug_dump = 0;
do_test();
}
T_DECL(malloc_stress, "Stress the heck out of malloc()")
{
setup_and_test();
}
T_DECL(malloc_stress_msl_full, "Stress the heck out of malloc() - enable malloc stack logging, full mode", T_META_ENVVAR("MallocStackLogging=1"))
{
setup_and_test();
}
T_DECL(malloc_stress_msl_malloc, "Stress the heck out of malloc() - enable malloc stack logging, malloc mode", T_META_ENVVAR("MallocStackLogging=malloc"))
{
setup_and_test();
}
T_DECL(malloc_stress_msl_vm, "Stress the heck out of malloc() - enable malloc stack logging, vm mode", T_META_ENVVAR("MallocStackLogging=vm"))
{
setup_and_test();
}
T_DECL(malloc_stress_msl_lite, "Stress the heck out of malloc() - enable malloc stack logging, lite mode", T_META_ENVVAR("MallocStackLogging=lite"))
{
setup_and_test();
}
#endif