1 /* 2 * This file and its contents are supplied under the terms of the 3 * Common Development and Distribution License ("CDDL"), version 1.0. 4 * You may only use this file in accordance with the terms of version 5 * 1.0 of the CDDL. 6 * 7 * A full copy of the text of the CDDL should have accompanied this 8 * source. A copy of the CDDL is also available via the Internet at 9 * http://www.illumos.org/license/CDDL. 10 */ 11 12 /* 13 * Copyright 2016 Joyent, Inc. 14 * Copyright 2024 Oxide Computer Company 15 */ 16 17 /* 18 * Basic tests for aligned_alloc(3C). Note that we test ENOMEM failure by 19 * relying on the implementation of the current libc malloc. Specifically we go 20 * through and add a mapping so we can't expand the heap and then use it up. If 21 * the memory allocator is ever changed, this test will start failing, at which 22 * point, it may not be worth the cost of keeping it around. 23 */ 24 25 #include <stdlib.h> 26 #include <errno.h> 27 #include <libproc.h> 28 #include <stdio.h> 29 #include <stdalign.h> 30 #include <err.h> 31 #include <sys/sysmacros.h> 32 #include <sys/mman.h> 33 #include <sys/debug.h> 34 #include <stdbool.h> 35 #include <string.h> 36 37 typedef struct { 38 size_t at_size; 39 size_t at_align; 40 int at_errno; 41 const char *at_desc; 42 } alloc_test_t; 43 44 static const alloc_test_t alloc_tests[] = { 45 { 0, sizeof (long), EINVAL, "zero alignment fails with EINVAL" }, 46 { sizeof (long), 0, EINVAL, "zero size fails with EINVAL" }, 47 { 128, 3, EINVAL, "3-byte alignment fails with EINVAL" }, 48 { 128, 7777, EINVAL, "7777-byte alignment fails with EINVAL" }, 49 { 128, 23, EINVAL, "23-byte alignment fails with EINVAL" }, 50 { sizeof (char), alignof (char), 0, "alignof (char), 1 byte" }, 51 { 5, alignof (char), 0, "alignof (char), multiple bytes" }, 52 { 1, alignof (short), 0, "alignof (short), 1 byte" }, 53 { 16, alignof (short), 0, "alignof (short), 16 byte" }, 54 { 1, alignof (int), 0, "alignof (int), 1 byte" }, 55 { 4, alignof (int), 0, "alignof (int), 4 bytes" }, 56 { 22, alignof (int), 0, "alignof (int), 22 bytes" }, 57 /* We skip long here because it varies between ILP32/LP64 */ 58 { 7, alignof (long long), 0, "alignof (long long), 7 bytes" }, 59 { 128, alignof (long long), 0, "alignof (long long), 128 bytes" }, 60 { 511, alignof (long long), 0, "alignof (long long), 511 bytes" }, 61 { 16, 16, 0, "16-byte alignment), 16 bytes" }, 62 { 256, 16, 0, "16-byte alignment), 256 bytes" }, 63 { 256, 4096, 0, "4096-byte alignment), 256 bytes" }, 64 { 4096, 4096, 0, "4096-byte alignment), 4096 bytes" }, 65 }; 66 67 /* 68 * Disable the per-thread caches and enable debugging if launched with umem. 69 */ 70 const char * 71 _umem_debug_init(void) 72 { 73 return ("default,verbose"); 74 } 75 76 const char * 77 _umem_options_init(void) 78 { 79 return ("perthreadcache=0"); 80 } 81 82 static bool 83 alloc_test_one(const alloc_test_t *test) 84 { 85 bool ret = false; 86 void *buf = aligned_alloc(test->at_align, test->at_size); 87 88 if (buf == NULL) { 89 if (test->at_errno == 0) { 90 warnx("TEST FAILED: %s: allocation failed with %s, but " 91 "expected success", test->at_desc, 92 strerrorname_np(errno)); 93 } else if (errno != test->at_errno) { 94 warnx("TEST FAILED: %s: allocation failed with %s, but " 95 "expected errno %s", test->at_desc, 96 strerrorname_np(errno), 97 strerrorname_np(test->at_errno)); 98 } else { 99 (void) printf("TEST PASSED: %s\n", test->at_desc); 100 ret = true; 101 } 102 } else if (test->at_errno != 0) { 103 warnx("TEST FAILED: %s: allocation succeeded, but expected " 104 "errno %s", test->at_desc, strerrorname_np(test->at_errno)); 105 } else { 106 (void) printf("TEST PASSED: %s\n", test->at_desc); 107 ret = true; 108 } 109 110 free(buf); 111 return (ret); 112 } 113 114 static bool 115 alloc_test_enomem(const alloc_test_t *test) 116 { 117 bool ret = false; 118 void *buf = aligned_alloc(test->at_align, test->at_size); 119 120 if (buf != NULL) { 121 warnx("TEST FAILED: %s (forced ENOMEM/EAGAIN): succeeded, but " 122 "expected ENOMEM", test->at_desc); 123 } else if (errno != ENOMEM && errno != EAGAIN) { 124 warnx("TEST FAILED: %s (forced ENOMEM/EAGAIN): failed with %s, " 125 "but expected ENOMEM", test->at_desc, 126 strerrordesc_np(errno)); 127 } else { 128 (void) printf("TEST PASSED: %s: ENOMEM/EAGAIN forced\n", 129 test->at_desc); 130 ret = true; 131 } 132 133 return (ret); 134 } 135 136 static void 137 libc_enomem(void) 138 { 139 pstatus_t status; 140 141 VERIFY0(proc_get_status(getpid(), &status)); 142 VERIFY3P(mmap((caddr_t)P2ROUNDUP(status.pr_brkbase + 143 status.pr_brksize, 0x1000), 0x1000, 144 PROT_READ, MAP_ANON | MAP_FIXED | MAP_PRIVATE, -1, 0), 145 !=, (void *)-1); 146 147 for (;;) { 148 if (malloc(16) == NULL) 149 break; 150 } 151 152 for (;;) { 153 if (aligned_alloc(sizeof (void *), 16) == NULL) 154 break; 155 } 156 } 157 158 /* 159 * Because this test is leveraging LD_PRELOAD in the test runner to test 160 * different malloc libraries, we can't call umem_setmtbf() directly. Instead we 161 * ask rtld to find it for us, which is a bit gross, but works. 162 */ 163 static void 164 libumem_enomem(void) 165 { 166 void (*mtbf)(uint32_t) = dlsym(RTLD_DEFAULT, "umem_setmtbf"); 167 if (mtbf == NULL) { 168 errx(EXIT_FAILURE, "failed to find umem_setmtbf: %s", 169 dlerror()); 170 } 171 172 mtbf(1); 173 } 174 175 int 176 main(void) 177 { 178 const char *preload; 179 int ret = EXIT_SUCCESS; 180 181 for (size_t i = 0; i < ARRAY_SIZE(alloc_tests); i++) { 182 if (!alloc_test_one(&alloc_tests[i])) { 183 ret = EXIT_FAILURE; 184 } 185 } 186 187 /* 188 * To catch failure tests, we need to know what memory allocator we're 189 * using which we expect to be indicated via an LD_PRELOAD environment 190 * variable. If it's not set, assume libc. 191 */ 192 preload = getenv("LD_PRELOAD"); 193 if (preload != NULL) { 194 if (strstr(preload, "umem") != NULL) { 195 libumem_enomem(); 196 } else { 197 warnx("malloc(3C) library %s not supported, skipping " 198 "ENOMEM tests", preload); 199 goto skip; 200 } 201 } else { 202 libc_enomem(); 203 } 204 205 for (size_t i = 0; i < ARRAY_SIZE(alloc_tests); i++) { 206 if (alloc_tests[i].at_errno != 0) 207 continue; 208 if (!alloc_test_enomem(&alloc_tests[i])) { 209 ret = EXIT_FAILURE; 210 } 211 } 212 213 skip: 214 if (ret == EXIT_SUCCESS) { 215 (void) printf("All tests passed successfully\n"); 216 } 217 218 return (ret); 219 } 220