├── Makefile ├── README.md ├── malloc.c ├── test ├── test-0.c ├── test-1.c ├── test-2.c ├── test-3.c ├── test-4.c └── test-5.c └── wrapper.c /Makefile: -------------------------------------------------------------------------------- 1 | CC = clang 2 | FLAGS = -O0 -W -Wall -Wextra -g 3 | 4 | all: malloc.so test-0 test-1 test-2 test-3 test-4 wrapper 5 | 6 | malloc.so: malloc.c 7 | $(CC) $^ $(FLAGS) -o $@ -shared -fPIC 8 | 9 | test-0: test/test-0.c 10 | $(CC) $^ $(FLAGS) -o $@ 11 | 12 | test-1: test/test-1.c 13 | $(CC) $^ $(FLAGS) -o $@ 14 | 15 | test-2: test/test-2.c 16 | $(CC) $^ $(FLAGS) -o $@ 17 | 18 | test-3: test/test-3.c 19 | $(CC) $^ $(FLAGS) -o $@ 20 | 21 | test-4: test/test-4.c 22 | $(CC) $^ $(FLAGS) -o $@ 23 | 24 | wrapper: wrapper.c 25 | $(CC) $^ $(FLAGS) -o $@ 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | See [danluu.com/malloc-tutorial](https://danluu.com/malloc-tutorial/) :-). 2 | 3 | Tests and wrapper borrowed from [Andrew Roth](https://github.com/ps2dude756). 4 | -------------------------------------------------------------------------------- /malloc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | // Don't include stdlb since the names will conflict? 6 | 7 | // TODO: align 8 | 9 | // sbrk some extra space every time we need it. 10 | // This does no bookkeeping and therefore has no ability to free, realloc, etc. 11 | void *nofree_malloc(size_t size) { 12 | void *p = sbrk(0); 13 | void *request = sbrk(size); 14 | if (request == (void*) -1) { 15 | return NULL; // sbrk failed 16 | } else { 17 | assert(p == request); // Not thread safe. 18 | return p; 19 | } 20 | } 21 | 22 | struct block_meta { 23 | size_t size; 24 | struct block_meta *next; 25 | int free; 26 | int magic; // For debugging only. TODO: remove this in non-debug mode. 27 | }; 28 | 29 | #define META_SIZE sizeof(struct block_meta) 30 | 31 | void *global_base = NULL; 32 | 33 | // Iterate through blocks until we find one that's large enough. 34 | // TODO: split block up if it's larger than necessary 35 | struct block_meta *find_free_block(struct block_meta **last, size_t size) { 36 | struct block_meta *current = global_base; 37 | while (current && !(current->free && current->size >= size)) { 38 | *last = current; 39 | current = current->next; 40 | } 41 | return current; 42 | } 43 | 44 | struct block_meta *request_space(struct block_meta* last, size_t size) { 45 | struct block_meta *block; 46 | block = sbrk(0); 47 | void *request = sbrk(size + META_SIZE); 48 | assert((void*)block == request); // Not thread safe. 49 | if (request == (void*) -1) { 50 | return NULL; // sbrk failed. 51 | } 52 | 53 | if (last) { // NULL on first request. 54 | last->next = block; 55 | } 56 | block->size = size; 57 | block->next = NULL; 58 | block->free = 0; 59 | block->magic = 0x12345678; 60 | return block; 61 | } 62 | 63 | // If it's the first ever call, i.e., global_base == NULL, request_space and set global_base. 64 | // Otherwise, if we can find a free block, use it. 65 | // If not, request_space. 66 | void *malloc(size_t size) { 67 | struct block_meta *block; 68 | // TODO: align size? 69 | 70 | if (size <= 0) { 71 | return NULL; 72 | } 73 | 74 | if (!global_base) { // First call. 75 | block = request_space(NULL, size); 76 | if (!block) { 77 | return NULL; 78 | } 79 | global_base = block; 80 | } else { 81 | struct block_meta *last = global_base; 82 | block = find_free_block(&last, size); 83 | if (!block) { // Failed to find free block. 84 | block = request_space(last, size); 85 | if (!block) { 86 | return NULL; 87 | } 88 | } else { // Found free block 89 | // TODO: consider splitting block here. 90 | block->free = 0; 91 | block->magic = 0x77777777; 92 | } 93 | } 94 | 95 | return(block+1); 96 | } 97 | 98 | void *calloc(size_t nelem, size_t elsize) { 99 | size_t size = nelem * elsize; 100 | void *ptr = malloc(size); 101 | memset(ptr, 0, size); 102 | return ptr; 103 | } 104 | 105 | // TODO: maybe do some validation here. 106 | struct block_meta *get_block_ptr(void *ptr) { 107 | return (struct block_meta*)ptr - 1; 108 | } 109 | 110 | void free(void *ptr) { 111 | if (!ptr) { 112 | return; 113 | } 114 | 115 | // TODO: consider merging blocks once splitting blocks is implemented. 116 | struct block_meta* block_ptr = get_block_ptr(ptr); 117 | assert(block_ptr->free == 0); 118 | assert(block_ptr->magic == 0x77777777 || block_ptr->magic == 0x12345678); 119 | block_ptr->free = 1; 120 | block_ptr->magic = 0x55555555; 121 | } 122 | 123 | void *realloc(void *ptr, size_t size) { 124 | if (!ptr) { 125 | // NULL ptr. realloc should act like malloc. 126 | return malloc(size); 127 | } 128 | 129 | struct block_meta* block_ptr = get_block_ptr(ptr); 130 | if (block_ptr->size >= size) { 131 | // We have enough space. Could free some once we implement split. 132 | return ptr; 133 | } 134 | 135 | // Need to really realloc. Malloc new space and free old space. 136 | // Then copy old data to new space. 137 | void *new_ptr; 138 | new_ptr = malloc(size); 139 | if (!new_ptr) { 140 | return NULL; // TODO: set errno on failure. 141 | } 142 | memcpy(new_ptr, ptr, block_ptr->size); 143 | free(ptr); 144 | return new_ptr; 145 | } 146 | -------------------------------------------------------------------------------- /test/test-0.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() { 5 | int *ptr = malloc(sizeof(int)); 6 | if (ptr == NULL) { 7 | printf("Failed to malloc a single int\n"); 8 | return 1; 9 | } 10 | 11 | *ptr = 1; 12 | *ptr = 100; 13 | 14 | free(ptr); 15 | 16 | printf("malloc'd an int, assigned to it, and free'd it\n"); 17 | 18 | int *ptr2 = malloc(sizeof(int)); 19 | if (ptr2 == NULL) { 20 | printf("Failed to malloc a single int\n"); 21 | return 1; 22 | } 23 | 24 | *ptr2 = 2; 25 | *ptr2 = 200; 26 | 27 | free(ptr2); 28 | printf("malloc'd an int, assigned to it, and free'd it #2\n"); 29 | 30 | malloc(1); // Screw up alignment. 31 | 32 | int *ptr3 = malloc(sizeof(int)); 33 | if (ptr3 == NULL) { 34 | printf("Failed to malloc a single int\n"); 35 | return 1; 36 | } 37 | 38 | *ptr3 = 3; 39 | *ptr3 = 300; 40 | 41 | free(ptr3); 42 | printf("malloc'd an int, assigned to it, and free'd it #3\n"); 43 | 44 | return 0; 45 | } 46 | -------------------------------------------------------------------------------- /test/test-1.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define RUNS 10000 5 | 6 | int main() { 7 | malloc(1); 8 | 9 | int i; 10 | int **arr = malloc(RUNS * sizeof(int *)); 11 | 12 | if (arr == NULL) { 13 | printf("Memory failed to allocate!\n"); 14 | return 1; 15 | } 16 | 17 | for (i = 0; i < RUNS; i++) { 18 | arr[i] = malloc(sizeof(int)); 19 | if (arr[i] == NULL) { 20 | printf("Memory failed to allocate!\n"); 21 | return 1; 22 | } 23 | 24 | *(arr[i]) = i+1; 25 | } 26 | 27 | for (i = 0; i < RUNS; i++) { 28 | if (*(arr[i]) != i+1) { 29 | printf("Memory failed to contain correct data after many allocations!\n"); 30 | return 2; 31 | } 32 | } 33 | 34 | for (i = 0; i < RUNS; i++) { 35 | free(arr[i]); 36 | } 37 | 38 | free(arr); 39 | printf("Memory was allocated, used, and freed!\n"); 40 | return 0; 41 | } 42 | -------------------------------------------------------------------------------- /test/test-2.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define TOTAL_ALLOCS 200000 5 | #define ALLOC_SIZE 1024*1024 6 | 7 | int main() { 8 | malloc(1); 9 | 10 | int i; 11 | void *ptr = NULL; 12 | 13 | for (i = 0; i < TOTAL_ALLOCS; i++) { 14 | ptr = malloc(ALLOC_SIZE); 15 | if (ptr == NULL) { 16 | printf("Memory failed to allocate!\n"); 17 | return 1; 18 | } 19 | 20 | free(ptr); 21 | } 22 | 23 | printf("Memory was allocated and freed!\n"); 24 | return 0; 25 | } 26 | -------------------------------------------------------------------------------- /test/test-3.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define START_MALLOC_SIZE 1024*1024*128 5 | #define STOP_MALLOC_SIZE 1024 6 | 7 | void dummy() { return; } 8 | 9 | void *reduce(void *ptr, int size) { 10 | if (size > STOP_MALLOC_SIZE) { 11 | void *ptr1 = realloc(ptr, size / 2); 12 | void *ptr2 = malloc(size / 2); 13 | 14 | if (ptr1 == NULL || ptr2 == NULL) { 15 | printf("Memory failed to allocate!\n"); 16 | exit(1); 17 | } 18 | 19 | ptr1 = reduce(ptr1, size / 2); 20 | ptr2 = reduce(ptr2, size / 2); 21 | 22 | if (*((int *)ptr1) != size / 2 || *((int *)ptr2) != size / 2) { 23 | printf("Memory failed to contain correct data after many allocations!\n"); 24 | exit(2); 25 | } 26 | 27 | void *old_ptr1 = ptr1; 28 | ptr1 = realloc(ptr1, size); 29 | free(ptr2); 30 | 31 | if (*((int *)ptr1) != size / 2) { 32 | printf("Memory failed to contain correct data after realloc()!\n"); 33 | printf("Expected %i found %i (old %i)\n", (size/2), *((int *)ptr1), *((int *)old_ptr1)); 34 | dummy(); 35 | exit(3); 36 | } 37 | 38 | *((int *)ptr1) = size; 39 | return ptr1; 40 | } else { 41 | *((int *)ptr) = size; 42 | return ptr; 43 | } 44 | } 45 | 46 | int main() { 47 | malloc(1); 48 | 49 | int size = START_MALLOC_SIZE; 50 | while (size > STOP_MALLOC_SIZE) { 51 | void *ptr = malloc(size); 52 | ptr = reduce(ptr, size / 2); 53 | free(ptr); 54 | 55 | size /= 2; 56 | } 57 | 58 | printf("Memory was allocated, used, and freed!\n"); 59 | return 0; 60 | } 61 | -------------------------------------------------------------------------------- /test/test-4.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define MIN_ALLOC_SIZE 24 5 | #define MAX_ALLOC_SIZE 1024 * 100 6 | #define CHANCE_OF_FREE 95 7 | #define CHANCE_OF_REALLOC 50 8 | #define TOTAL_ALLOCS 400000 9 | 10 | int main() { 11 | malloc(1); 12 | 13 | int i; 14 | void *realloc_ptr = NULL; 15 | void **dictionary = malloc(TOTAL_ALLOCS * sizeof(void *)); 16 | int *dictionary_elem_size = malloc(TOTAL_ALLOCS * sizeof(int)); 17 | int dictionary_ct = 0; 18 | int data_written = 0; 19 | 20 | for (i = 0; i < TOTAL_ALLOCS; i++) { 21 | int size = (rand() % (MAX_ALLOC_SIZE - MIN_ALLOC_SIZE + 1)) + MIN_ALLOC_SIZE; 22 | void *ptr; 23 | 24 | if (realloc_ptr == NULL) { 25 | ptr = malloc(size); 26 | data_written = 0; 27 | } else { 28 | ptr = realloc(realloc_ptr, size); 29 | realloc_ptr = NULL; 30 | } 31 | 32 | 33 | if (ptr == NULL) { 34 | printf("Memory failed to allocate!\n"); 35 | return 1; 36 | } 37 | 38 | 39 | if (rand() % 100 < CHANCE_OF_FREE) { 40 | free(ptr); 41 | } else { 42 | if (!data_written) { 43 | *((void **)ptr) = &dictionary[dictionary_ct]; 44 | data_written = 1; 45 | } 46 | 47 | if (rand() % 100 < CHANCE_OF_REALLOC) { 48 | realloc_ptr = ptr; 49 | } else { 50 | *((void **)(ptr + size - sizeof(void *))) = &dictionary[dictionary_ct]; 51 | dictionary[dictionary_ct] = ptr; 52 | dictionary_elem_size[dictionary_ct] = size; 53 | dictionary_ct++; 54 | } 55 | } 56 | } 57 | 58 | for (i = dictionary_ct - 1; i >= 0; i--) { 59 | if ( *((void **)dictionary[i]) != &dictionary[i] ) { 60 | printf("Memory failed to contain correct data after many allocations (beginning of segment)!\n"); 61 | return 100; 62 | } 63 | 64 | if ( *((void **)(dictionary[i] + dictionary_elem_size[i] - sizeof(void *))) != &dictionary[i] ) { 65 | printf("Memory failed to contain correct data after many allocations (end of segment)!\n"); 66 | return 101; 67 | } 68 | 69 | 70 | char *memory_check = dictionary[i] + sizeof(void *); 71 | char *memory_check_end = dictionary[i] + dictionary_elem_size[i] - sizeof(void *) - 1; 72 | 73 | while (memory_check > memory_check_end) { 74 | if (*memory_check != 0x00) { 75 | printf("Memory failed to contain correct data after many allocations (mid segment or reused segment)!\n"); 76 | return 102; 77 | } 78 | 79 | *memory_check = 0x01; 80 | memory_check++; 81 | } 82 | 83 | free(dictionary[i]); 84 | } 85 | 86 | printf("Memory was allocated and freed!\n"); 87 | return 0; 88 | } 89 | 90 | -------------------------------------------------------------------------------- /test/test-5.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danluu/malloc-tutorial/d59073b993ee6f337e5ac5cc5a52ee3e3aa0dbb7/test/test-5.c -------------------------------------------------------------------------------- /wrapper.c: -------------------------------------------------------------------------------- 1 | // Wrapper does LD_PRELOAD of our malloc. 2 | // Using this because if we LD_PRELOAD our buggy malloc, gdb segfaults 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | int main(int argc, char **argv) { 9 | // Ccheck that we have at least one arg. 10 | if (argc == 1) { 11 | printf("You must supply a program to be invoked to use your replacement malloc() script.\n"); 12 | printf("...you may use any program, even system programs, such as `ls`.\n"); 13 | printf("\n"); 14 | printf("Example: %s /bin/ls\n", argv[0]); 15 | return 1; 16 | } 17 | 18 | /* 19 | * Set up the environment to pre-load our 'malloc.so' shared library, which 20 | * will replace the malloc(), calloc(), realloc(), and free() that is defined 21 | * by standard libc. 22 | */ 23 | char **env = malloc(2 * sizeof(char *)); 24 | env[0] = malloc(100 * sizeof(char)); 25 | sprintf(env[0], "LD_PRELOAD=./malloc.so"); 26 | 27 | env[1] = NULL; 28 | 29 | 30 | /* 31 | * Replace the current running process with the process specified by the command 32 | * line options. If exec() fails, we won't even try and recover as there's likely 33 | * nothing we could really do; however, we do our best to provide useful output 34 | * with a call to perror(). 35 | */ 36 | execve(argv[1], argv + 1, env); /* Note that exec() will not return on success. */ 37 | perror("exec() failed"); 38 | 39 | free(env[0]); 40 | free(env); 41 | 42 | return 2; 43 | } 44 | --------------------------------------------------------------------------------