OMCompiler/SimulationRuntime/c/util/omc_stackoverflow.c
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * This file belongs to the OpenModelica Run-Time System | ||
| 3 | * | ||
| 4 | * Copyright (c) 1998-2026, Open Source Modelica Consortium (OSMC), c/o Linköpings | ||
| 5 | * universitet, Department of Computer and Information Science, SE-58183 Linköping, Sweden. All rights | ||
| 6 | * reserved. | ||
| 7 | * | ||
| 8 | * THIS PROGRAM IS PROVIDED UNDER THE TERMS OF THE BSD NEW LICENSE OR THE | ||
| 9 | * AGPL VERSION 3 LICENSE OR THE OSMC PUBLIC LICENSE (OSMC-PL) VERSION 1.8. ANY | ||
| 10 | * USE, REPRODUCTION OR DISTRIBUTION OF THIS PROGRAM CONSTITUTES RECIPIENT'S | ||
| 11 | * ACCEPTANCE OF THE BSD NEW LICENSE OR THE OSMC PUBLIC LICENSE OR THE AGPL | ||
| 12 | * VERSION 3, ACCORDING TO RECIPIENTS CHOICE. | ||
| 13 | * | ||
| 14 | * The OpenModelica software and the OSMC (Open Source Modelica Consortium) Public License | ||
| 15 | * (OSMC-PL) are obtained from OSMC, either from the above address, from the URLs: | ||
| 16 | * http://www.openmodelica.org or https://github.com/OpenModelica/ or | ||
| 17 | * http://www.ida.liu.se/projects/OpenModelica, and in the OpenModelica distribution. GNU | ||
| 18 | * AGPL version 3 is obtained from: https://www.gnu.org/licenses/licenses.html#GPL. The BSD NEW | ||
| 19 | * License is obtained from: http://www.opensource.org/licenses/BSD-3-Clause. | ||
| 20 | * | ||
| 21 | * This program is distributed WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 22 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, EXCEPT AS EXPRESSLY | ||
| 23 | * SET FORTH IN THE BY RECIPIENT SELECTED SUBSIDIARY LICENSE CONDITIONS OF | ||
| 24 | * OSMC-PL. | ||
| 25 | * | ||
| 26 | */ | ||
| 27 | |||
| 28 | /* Stack overflow handling */ | ||
| 29 | |||
| 30 | #if (defined(__linux__) || defined(__FreeBSD__)) && !defined(_GNU_SOURCE) | ||
| 31 | #define _GNU_SOURCE 1 | ||
| 32 | /* for pthread_getattr_np */ | ||
| 33 | #endif | ||
| 34 | |||
| 35 | #if defined(__FreeBSD__) | ||
| 36 | #include <pthread_np.h> | ||
| 37 | #endif | ||
| 38 | |||
| 39 | #include "../gc/omc_gc.h" | ||
| 40 | #include "omc_init.h" | ||
| 41 | |||
| 42 | 10418 | void mmc_clearStacktraceMessages(threadData_t *threadData) | |
| 43 | { | ||
| 44 | 10418 | threadData->localRoots[LOCAL_ROOT_STACK_OVERFLOW] = 0; | |
| 45 | 10418 | } | |
| 46 | |||
| 47 | 140 | int mmc_hasStacktraceMessages(threadData_t *threadData) | |
| 48 | { | ||
| 49 | 140 | return threadData->localRoots[LOCAL_ROOT_STACK_OVERFLOW] != 0; | |
| 50 | } | ||
| 51 | |||
| 52 | #if (defined(__linux__) && defined(__GLIBC__)) || defined(__APPLE__) || defined(__FreeBSD__) | ||
| 53 | #include <stdlib.h> | ||
| 54 | #include <stdio.h> | ||
| 55 | #include <signal.h> | ||
| 56 | #include <stdio.h> | ||
| 57 | #include <signal.h> | ||
| 58 | #include <execinfo.h> | ||
| 59 | #include <sys/time.h> | ||
| 60 | #include <sys/resource.h> | ||
| 61 | #include <assert.h> | ||
| 62 | #include <pthread.h> | ||
| 63 | #include <setjmp.h> | ||
| 64 | #include <unistd.h> | ||
| 65 | |||
| 66 | /* If we find a SIGSEGV near the end of the stack, it is probably due to a stack overflow. 64kB for a function frame seems reasonable. */ | ||
| 67 | #define LIMIT_FOR_STACK_OVERFLOW 65536 | ||
| 68 | |||
| 69 | static void *trace[MMC_SEGV_TRACE_NFRAMES]; | ||
| 70 | static int trace_size; | ||
| 71 | static int trace_size_skip=0; /* First index we should use; that is skip handler, etc */ | ||
| 72 | static struct sigaction default_segv_action; | ||
| 73 | |||
| 74 | ✗ | void printStacktraceMessages(void) { | |
| 75 | int i,j=-1,k; | ||
| 76 | ✗ | char **messages = backtrace_symbols(trace, trace_size); | |
| 77 | ✗ | fprintf(stderr,"[bt] Execution path:\n"); | |
| 78 | ✗ | for (i=trace_size_skip; i<trace_size; ++i) | |
| 79 | { | ||
| 80 | ✗ | if (i<trace_size-1 && trace[i] == trace[i+1]) { | |
| 81 | ✗ | j=j==-1?i:j; | |
| 82 | ✗ | } else if (j>=0) { | |
| 83 | ✗ | k=19-fprintf(stderr,"[bt] #%d..%d", j-trace_size_skip, i-trace_size_skip); | |
| 84 | ✗ | while (k-->0) fprintf(stderr, " "); | |
| 85 | ✗ | fprintf(stderr,"%s\n", messages[i]); | |
| 86 | j=-1; | ||
| 87 | } else { | ||
| 88 | ✗ | k=19-fprintf(stderr,"[bt] #%d ", i-trace_size_skip); | |
| 89 | ✗ | while (k-->0) fprintf(stderr, " "); | |
| 90 | ✗ | fprintf(stderr,"%s\n", messages[i]); | |
| 91 | } | ||
| 92 | } | ||
| 93 | ✗ | if (trace_size==MMC_SEGV_TRACE_NFRAMES) { | |
| 94 | ✗ | fprintf(stderr,"[bt] [...]\n"); | |
| 95 | } | ||
| 96 | ✗ | free(messages); | |
| 97 | ✗ | } | |
| 98 | |||
| 99 | ✗ | void mmc_setStacktraceMessages(int numSkip, int numFrames) { | |
| 100 | ✗ | trace_size = 0; | |
| 101 | ✗ | trace_size = backtrace(trace, numFrames == 0 ? MMC_SEGV_TRACE_NFRAMES : numFrames > MMC_SEGV_TRACE_NFRAMES ? MMC_SEGV_TRACE_NFRAMES : numFrames); | |
| 102 | ✗ | trace_size_skip = numSkip; | |
| 103 | ✗ | } | |
| 104 | |||
| 105 | |||
| 106 | static sigset_t segvset; | ||
| 107 | |||
| 108 | ✗ | static void handler(int signo, siginfo_t *si, void *ptr) | |
| 109 | { | ||
| 110 | int unused __attribute__((unused)), isStackOverflow; | ||
| 111 | ✗ | threadData_t *threadData = (threadData_t*)pthread_getspecific(mmc_thread_data_key); | |
| 112 | ✗ | isStackOverflow = si->si_addr < threadData->stackBottom | |
| 113 | ✗ | && (char*)si->si_addr > (char*)threadData->stackBottom - LIMIT_FOR_STACK_OVERFLOW; | |
| 114 | if (isStackOverflow) { | ||
| 115 | ✗ | mmc_setStacktraceMessages(1,0); | |
| 116 | ✗ | sigprocmask(SIG_UNBLOCK, &segvset, NULL); | |
| 117 | ✗ | longjmp(*threadData->mmc_stack_overflow_jumper,1); | |
| 118 | } | ||
| 119 | /* This backtrace uses very little stack-space, and segmentation faults we always want to print... */ | ||
| 120 | ✗ | mmc_setStacktraceMessages(1,16); | |
| 121 | ✗ | unused=write(2, "\nLimited backtrace at point of segmentation fault\n", 50); | |
| 122 | ✗ | backtrace_symbols_fd(trace+trace_size_skip, trace_size-trace_size_skip, 2); | |
| 123 | ✗ | sigaction(SIGSEGV, &default_segv_action, 0); | |
| 124 | ✗ | } | |
| 125 | |||
| 126 | #if defined(__APPLE__) | ||
| 127 | #include <AvailabilityMacros.h> | ||
| 128 | #include <sys/sysctl.h> | ||
| 129 | #endif | ||
| 130 | |||
| 131 | 68436 | static void* getStackBase(void) { | |
| 132 | /* Warning: These functions are highly non-portable and are recommended to not be used. | ||
| 133 | * We only tested them on Linux and OSX. | ||
| 134 | * On OSX we get the top of the stack and the size | ||
| 135 | * On Linux we get the bottom, so we don't need the size... YMMV | ||
| 136 | */ | ||
| 137 | void* stackBottom; | ||
| 138 | 68436 | pthread_t self = pthread_self(); | |
| 139 | #if !defined(__APPLE__) | ||
| 140 | 68436 | size_t size = 0; | |
| 141 | pthread_attr_t sattr; | ||
| 142 | 68436 | pthread_attr_init(&sattr); | |
| 143 | #if defined(__FreeBSD__) | ||
| 144 | assert(0==pthread_attr_get_np(self, &sattr)); | ||
| 145 | #else // normal Linux | ||
| 146 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 68436 times.
|
68436 | assert(0==pthread_getattr_np(self, &sattr)); |
| 147 | #endif | ||
| 148 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 68436 times.
|
68436 | assert(0==pthread_attr_getstack(&sattr, &stackBottom, &size)); |
| 149 | #if defined(__FreeBSD__) | ||
| 150 | // if we get a 0 stackBottom, try a different strategy | ||
| 151 | if (!stackBottom) { | ||
| 152 | void* stack_addr = NULL; | ||
| 153 | assert(0==pthread_attr_getstackaddr(&sattr, &stack_addr)); | ||
| 154 | // if we get a 0 as stack address, all bets are off | ||
| 155 | assert(stack_addr); | ||
| 156 | assert(0==pthread_attr_getstacksize(&sattr, &size)); | ||
| 157 | stackBottom = (void*) (((long)stack_addr) - size); | ||
| 158 | } | ||
| 159 | #endif | ||
| 160 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 68436 times.
|
68436 | assert(stackBottom); |
| 161 | 68436 | pthread_attr_destroy(&sattr); | |
| 162 | #else | ||
| 163 | void* stack_addr = pthread_get_stackaddr_np(self); | ||
| 164 | size_t size = pthread_get_stacksize_np(self); | ||
| 165 | stackBottom = (void*) (((long)stack_addr) - size); | ||
| 166 | /* This workaround is only built if compiling for/on legacy OS X 10.9 (Mavericks) or older */ | ||
| 167 | #if defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED <= 1090 | ||
| 168 | if (pthread_main_np()) { | ||
| 169 | char str[256]; | ||
| 170 | size_t size_str = sizeof(str); | ||
| 171 | int ret = sysctlbyname("kern.osrelease", str, &size_str, NULL, 0); | ||
| 172 | if (0==ret && str[0]=='1' && str[1]=='3' && str[2]=='.') { | ||
| 173 | /* OSX Mavericks returns a wrong stack size... Assume default 8MB */ | ||
| 174 | stackBottom = (void*) (((long)stack_addr) - 8 * 1024 * 1024); | ||
| 175 | } | ||
| 176 | } | ||
| 177 | #endif | ||
| 178 | #endif | ||
| 179 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 68436 times.
|
68436 | assert(size > 128*1024); |
| 180 | 68436 | return (char*)stackBottom + 64*1024; | |
| 181 | } | ||
| 182 | |||
| 183 | /* The alternate signal stack has to outlive every frame that could fault on it, | ||
| 184 | so it is allocated once and never freed; the handle keeps it reachable. */ | ||
| 185 | static char *segv_altstack = NULL; | ||
| 186 | |||
| 187 | 3847 | void init_metamodelica_segv_handler(void) | |
| 188 | { | ||
| 189 | stack_t ss; | ||
| 190 | 3847 | struct sigaction sa = { | |
| 191 | .sa_sigaction = handler, | ||
| 192 | .sa_flags = SA_ONSTACK | SA_SIGINFO | ||
| 193 | }; | ||
| 194 | |||
| 195 | 3847 | segv_altstack = (char*) malloc(SIGSTKSZ); | |
| 196 | 3847 | ss.ss_size = SIGSTKSZ; | |
| 197 | 3847 | ss.ss_sp = segv_altstack; | |
| 198 | 3847 | ss.ss_flags = 0; | |
| 199 | 3847 | sigaltstack(&ss, 0); | |
| 200 | 3847 | sigfillset(&sa.sa_mask); | |
| 201 | 3847 | sigaction(SIGSEGV, &sa, &default_segv_action); | |
| 202 | 3847 | sigfillset(&segvset); | |
| 203 | 3847 | } | |
| 204 | |||
| 205 | 68436 | void mmc_init_stackoverflow(threadData_t *threadData) | |
| 206 | { | ||
| 207 | /* Keep a known bottom: a library omc dlopens with RTLD_DEEPBIND has its own | ||
| 208 | mmc_thread_data_key, so its in_<fn> wrapper lands here with omc's | ||
| 209 | threadData, and a bottom taken from this deep frame trips the next MMC_SO(). */ | ||
| 210 |
1/2✓ Branch 0 taken 68436 times.
✗ Branch 1 not taken.
|
68436 | if (threadData->stackBottom) { |
| 211 | return; | ||
| 212 | } | ||
| 213 | 68436 | threadData->stackBottom = getStackBase(); | |
| 214 | } | ||
| 215 | |||
| 216 | #else | ||
| 217 | |||
| 218 | void printStacktraceMessages() | ||
| 219 | { | ||
| 220 | } | ||
| 221 | |||
| 222 | void init_metamodelica_segv_handler(void) | ||
| 223 | { | ||
| 224 | } | ||
| 225 | |||
| 226 | void mmc_init_stackoverflow(threadData_t *threadData) | ||
| 227 | { | ||
| 228 | threadData->stackBottom = (void *)0x1; /* Dummy until Windows detects the stack bottom */ | ||
| 229 | } | ||
| 230 | #endif | ||
| 231 | |||
| 232 | /* omc records the trace as a MetaModelica list, through a hook meta/ installs | ||
| 233 | (meta_modelica_segv.c); a simulation leaves it unset. */ | ||
| 234 | static void (*stacktrace_capture)(threadData_t*, int, int) = NULL; | ||
| 235 | |||
| 236 | 2677 | void omc_set_stacktrace_capture(void (*fn)(threadData_t*, int, int)) | |
| 237 | { | ||
| 238 | 2677 | stacktrace_capture = fn; | |
| 239 | 2677 | } | |
| 240 | |||
| 241 | ✗ | void mmc_do_stackoverflow(threadData_t *threadData) | |
| 242 | { | ||
| 243 | ✗ | if (stacktrace_capture) { | |
| 244 | ✗ | stacktrace_capture(threadData, 1, MMC_SEGV_TRACE_NFRAMES); | |
| 245 | } | ||
| 246 | ✗ | longjmp(*threadData->mmc_stack_overflow_jumper, 1); | |
| 247 | } | ||
| 248 |