Line data Source code
1 : /** @file oomstub.c 2 : * @brief C memory management stubs 3 : * 4 : * This file implements fake malloc, free and realloc functions which can 5 : * fail and simulate OOM after a countdown. 6 : * This library is not threadsafe. 7 : * Inspired from https://github.com/bkthomps/Containers test suite 8 : * 9 : * @author François Cerbelle (Fanfan), francois@cerbelle.net 10 : * 11 : * @internal 12 : * Created: 10/12/2024 13 : * Revision: none 14 : * Last modified: 2024-12-11 19:45 15 : * Compiler: gcc 16 : * Organization: Cerbelle.net 17 : * Copyright: Copyright (c) 2024, François Cerbelle 18 : * 19 : * This source code is released for free distribution under the terms of the 20 : * GNU General Public License as published by the Free Software Foundation. 21 : */ 22 : 23 : #include "liboom/oomstub.h" 24 : #include <stdlib.h> /* NULL */ 25 : #include <dlfcn.h> 26 : 27 : #ifndef RTLD_NEXT 28 : #define RTLD_NEXT ((void *) -1L) 29 : #endif 30 : 31 : /** Countdown before malloc failure 32 : * The value is decremented at each malloc call. I use a single countdown 33 : * shared between all stub functions because the compiler can optimize the code 34 : * and choose to directly use a malloc call instead of a realloc(NULL,xyz). 35 : */ 36 : static int oomstub_countdown = 0; 37 : 38 : /** Functor to store the original malloc function 39 : * This functor saves the original malloc function to call if no failure is 40 : * expected. 41 : */ 42 : static void *(*real_malloc)(size_t); 43 : 44 : /** Functor to store the original calloc function 45 : * This functor saves the original calloc function to call if no failure is 46 : * expected. 47 : */ 48 : static void *(*real_calloc)(size_t, size_t); 49 : 50 : /** Functor to store the original realloc function 51 : * This functor saves the original realloc function to call if no failure is 52 : * expected. 53 : */ 54 : static void *(*real_realloc)(void *, size_t); 55 : 56 : /** Sets the malloc countdown before triggering a failure 57 : * @param counter Value to set in the internal countdown 58 : * @see malloc 59 : * @see calloc 60 : * @see realloc 61 : */ 62 108 : void oomstub_setcountdown(const int counter) { 63 108 : oomstub_countdown = counter; 64 108 : } 65 : 66 : /** Gets the current malloc countdown before triggering a failure 67 : * @return The current internal countdown value 68 : * @see malloc 69 : * @see calloc 70 : * @see realloc 71 : */ 72 108 : int oomstub_getcountdown() { 73 108 : return oomstub_countdown; 74 : } 75 : 76 : /** stdc malloc stub function 77 : * @param size Size in bytes to try to allocate 78 : * @return Pointer to the allocated heap block 79 : * @retval NULL The allocation failed 80 : * 81 : * This function is called instead of the upstream malloc function. If not 82 : * already done, it creates a backup ot the upstream function pointer. If the 83 : * countdown equals 1, it simulates OOM and returns NULL. Otherwise, it calls 84 : * the upstream malloc and returns its return value. Each call to this stub 85 : * function decrements the countdown until it reaches 0. 86 : */ 87 : void *malloc(size_t size) { 88 1119092 : if (!real_malloc) { 89 367 : *(void **) (&real_malloc) = dlsym(RTLD_NEXT, "malloc"); 90 : } 91 1119092 : if (oomstub_countdown == 1) { 92 6 : oomstub_countdown = 0; 93 6 : return NULL; 94 : } 95 1119086 : if (oomstub_countdown > 0) { 96 6 : oomstub_countdown--; 97 : } 98 1119086 : return real_malloc(size); 99 : } 100 : 101 : /** stdc calloc stub function 102 : * @param count How many contiguous records to allocate 103 : * @param size Size in bytes of a single record 104 : * @return Pointer to the allocated heap block 105 : * @retval NULL The allocation failed 106 : * 107 : * This function is called instead of the upstream calloc function. If not 108 : * already done, it creates a backup ot the upstream function pointer. If the 109 : * countdown equals 1, it simulates OOM and returns NULL. Otherwise, it calls 110 : * the upstream calloc and returns its return value. Each call to this stub 111 : * function decrements the countdown until it reaches 0. 112 : */ 113 : void *calloc(size_t count, size_t size) { 114 9 : if (!real_calloc) { 115 3 : *(void **) (&real_calloc) = dlsym(RTLD_NEXT, "calloc"); 116 : } 117 9 : if (oomstub_countdown == 1) { 118 3 : oomstub_countdown = 0; 119 3 : return NULL; 120 : } 121 6 : if (oomstub_countdown > 0) { 122 3 : oomstub_countdown--; 123 : } 124 6 : return real_calloc(count, size); 125 : } 126 : 127 : /** stdc realloc stub function 128 : * @param ptr Pointer to the dynamic heap block to resize 129 : * @param new_size New size in bytes to request 130 : * @return Pointer to the reallocated heap block 131 : * @retval NULL The allocation failed (important details in description) 132 : * 133 : * This function is called instead of the upstream realloc function. If not 134 : * already done, it creates a backup ot the upstream function pointer. If the 135 : * countdown equals 1, it simulates OOM and returns NULL. Otherwise, it calls 136 : * the upstream realloc and returns its return value. Each call to this stub 137 : * function decrements the countdown until it reaches 0. 138 : * 139 : * @note The failure countdown should not apply when shrinking the size. It is 140 : * meaningless as it can not fail in real life. It would need bookkeeping to 141 : * detect arbitrary shrinks. Thus, this stub function also counts shrinking 142 : * calls in the countdown and apply failures. 143 : * The tradeof is that the function returns NULL in case of a simulated failure 144 : * and a pointer in case of success for any case but not for the "free" case. 145 : * In the "free" case (new_size==0), it returns a not-null value in case of 146 : * failure and NULL in case of success. 147 : * 148 : * @warning if the compiler optimizes a realloc(ptr,0) with a direct free(ptr) 149 : * call, the countdown is not decremented. I can not detect this condition and 150 : * I have no tradeoff/workaround. Diverting free() would decrement the 151 : * countdown when not expected. 152 : */ 153 : void *realloc(void *ptr, size_t new_size) { 154 6825 : if (!real_realloc) { 155 367 : *(void **) (&real_realloc) = dlsym(RTLD_NEXT, "realloc"); 156 : } 157 6825 : if (oomstub_countdown == 1) { 158 9 : oomstub_countdown = 0; 159 9 : if (new_size > 0) 160 6 : return NULL; 161 : else 162 3 : return ptr; 163 : } 164 6816 : if (oomstub_countdown > 0) { 165 9 : oomstub_countdown--; 166 : } 167 : 168 6816 : return real_realloc(ptr, new_size); 169 : } 170 : 171 : /* vim: set tw=80: */