1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22 /*
23 * Copyright 2011 Nexenta Systems, Inc. All rights reserved.
24 */
25 /*
26 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
27 * Use is subject to license terms.
28 */
29 /*
30 * Copyright 2017 John R. Marino <draco@marino.st>
31 */
32
33 #pragma weak __fex_get_log = fex_get_log
34 #pragma weak __fex_set_log = fex_set_log
35 #pragma weak __fex_get_log_depth = fex_get_log_depth
36 #pragma weak __fex_set_log_depth = fex_set_log_depth
37 #pragma weak __fex_log_entry = fex_log_entry
38
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <unistd.h>
42 #include <string.h>
43 #include <signal.h>
44 #include <ucontext.h>
45 #include <machine/frame.h>
46 #include <fenv.h>
47 #include <sys/ieeefp.h>
48 #include <pthread.h>
49 #include "fex_handler.h"
50
51 #if defined __DragonFly__ || defined __FreeBSD__
52 #define REG_PC mc_rip
53 #define REG_SP mc_rsp
54 #define REG_FP mc_rbp
55 #define FRAME_STRUCTURE struct trapframe
56 #define FRAMEP(X) (struct trapframe *)(X)
57 #define NEXT_FRAME(fp) (struct trapframe *)fp->tf_rbp
58 #define NEXT_STACK(fp) fp->tf_rip
59 # ifdef __FreeBSD__
60 #define FPU_STATE mc_fpstate
61 #define FPU_STRUCTURE savefpu
62 # endif
63 # ifdef __DragonFly__
64 #define FPU_STATE mc_fpregs
65 #define FPU_STRUCTURE savexmm64
66 # endif
67 #else
68 #error Fex log not supported on this platform
69 #endif
70
71 #define SUPPORT_STACK_WALKING 0
72
73 static FILE *log_fp = NULL;
74 static pthread_mutex_t log_lock = PTHREAD_MUTEX_INITIALIZER;
75 static int log_depth = 100;
76
fex_get_log(void)77 FILE *fex_get_log(void)
78 {
79 FILE *fp;
80
81 pthread_mutex_lock(&log_lock);
82 fp = log_fp;
83 pthread_mutex_unlock(&log_lock);
84 return fp;
85 }
86
fex_set_log(FILE * fp)87 int fex_set_log(FILE *fp)
88 {
89 pthread_mutex_lock(&log_lock);
90 log_fp = fp;
91 pthread_mutex_unlock(&log_lock);
92 __fex_update_te();
93 return 1;
94 }
95
fex_get_log_depth(void)96 int fex_get_log_depth(void)
97 {
98 int d;
99
100 pthread_mutex_lock(&log_lock);
101 d = log_depth;
102 pthread_mutex_unlock(&log_lock);
103 return d;
104 }
105
fex_set_log_depth(int d)106 int fex_set_log_depth(int d)
107 {
108 if (d < 0)
109 return 0;
110 pthread_mutex_lock(&log_lock);
111 log_depth = d;
112 pthread_mutex_unlock(&log_lock);
113 return 1;
114 }
115
116 static struct exc_list {
117 struct exc_list *next;
118 char *addr;
119 unsigned long code;
120 int nstack;
121 char *stack[1]; /* actual length is max(1,nstack) */
122 } *list = NULL;
123
124 // amd64
125 #define PDIG "16"
126
127 /* look for a matching exc_list; return 1 if one is found,
128 otherwise add this one to the list and return 0 */
check_exc_list(char * addr,unsigned long code,char * stk,FRAME_STRUCTURE * fp)129 static int check_exc_list(char *addr, unsigned long code, char *stk,
130 FRAME_STRUCTURE *fp)
131 {
132 struct exc_list *l, *ll = NULL;
133 FRAME_STRUCTURE *f;
134 int i, n;
135
136 if (list) {
137 for (l = list; l; ll = l, l = l->next) {
138 if (l->addr != addr || l->code != code)
139 continue;
140 if (log_depth < 1 || l->nstack < 1)
141 return 1;
142 if (l->stack[0] != stk)
143 continue;
144 n = 1;
145 for (i = 1, f = fp;
146 i < log_depth && i < l->nstack && NEXT_STACK(f);
147 i++, f = NEXT_FRAME(f))
148 if (l->stack[i] != (char *)NEXT_STACK(f)) {
149 n = 0;
150 break;
151 }
152 if (n)
153 return 1;
154 }
155 }
156
157 /* create a new exc_list structure and tack it on the list */
158 for (n = 1, f = fp;
159 n < log_depth && f && NEXT_STACK(f);
160 n++, f = NEXT_FRAME(f));
161 if ((l = (struct exc_list *)malloc(sizeof(struct exc_list) +
162 (n - 1) * sizeof(char *))) != NULL) {
163 l->next = NULL;
164 l->addr = addr;
165 l->code = code;
166 l->nstack = ((log_depth < 1)? 0 : n);
167 l->stack[0] = stk;
168 for (i = 1; i < n; i++) {
169 l->stack[i] = (char *)NEXT_STACK(fp);
170 fp = NEXT_FRAME(f);
171 }
172 if (list)
173 ll->next = l;
174 else
175 list = l;
176 }
177 return 0;
178 }
179
180 /*
181 * Warning: cleverness ahead
182 *
183 * In the following code, the use of sprintf+write rather than fprintf
184 * to send output to the log file is intentional. The reason is that
185 * fprintf is not async-signal-safe. "But," you protest, "SIGFPE is
186 * not an asynchronous signal! It's always handled by the same thread
187 * that executed the fpop that provoked it." That's true, but a prob-
188 * lem arises because (i) base conversion in fprintf can cause a fp
189 * exception and (ii) my signal handler acquires a mutex lock before
190 * sending output to the log file (so that outputs for entries from
191 * different threads aren't interspersed). Therefore, if the code
192 * were to use fprintf, a deadlock could occur as follows:
193 *
194 * Thread A Thread B
195 *
196 * Incurs a fp exception, Calls fprintf,
197 * acquires log_lock acquires file rmutex lock
198 *
199 * Calls fprintf, Incurs a fp exception,
200 * waits for file rmutex lock waits for log_lock
201 *
202 * (I could just verify that fprintf doesn't hold the rmutex lock while
203 * it's doing the base conversion, but since efficiency is of little
204 * concern here, I opted for the safe and dumb route.)
205 */
206
print_stack(int fd,char * addr,FRAME_STRUCTURE * fp)207 static void print_stack(int fd, char *addr, FRAME_STRUCTURE *fp)
208 {
209 int i;
210 char buf[30];
211 char *line;
212
213 for (i = 0; i < log_depth && addr != NULL; i++) {
214 line = convert_address_to_symbol (addr);
215 write(fd, buf, sprintf(buf, " 0x%0" PDIG "lx ", (long)addr));
216 write(fd, line, strlen(line));
217 write(fd, "\n", 1);
218 /* temp -- figure out how to detect main
219 if (!strcmp(name, "main"))
220 break;
221 */
222 if (fp == NULL)
223 break;
224 addr = (char *)NEXT_STACK(fp);
225 fp = NEXT_FRAME(fp);
226 }
227 }
228
229 static void
print_previous_two_stack_addresses(int fd,char * addr,FRAME_STRUCTURE * fp)230 print_previous_two_stack_addresses (int fd, char *addr, FRAME_STRUCTURE *fp) {
231 char buf[30];
232 char *line;
233 char *next_addr;
234 const char *crtstuff = "_start ";
235
236 line = convert_address_to_symbol (addr);
237 write(fd, buf, sprintf(buf, " 0x%0" PDIG "lx ", (long)addr));
238 write(fd, line, strlen(line));
239 write(fd, "\n", 1);
240 /* Don't print previous frame if it starts with "_start" symbol */
241 next_addr = (char *)fp->tf_rsi;
242 line = convert_address_to_symbol (next_addr);
243 if (strncmp(line, crtstuff, 7) == 0) {
244 return;
245 }
246 write(fd, buf, sprintf(buf, " 0x%0" PDIG "lx ", (long)next_addr));
247 write(fd, line, strlen(line));
248 write(fd, "\n", 1);
249 }
250
fex_log_entry(const char * msg)251 void fex_log_entry(const char *msg)
252 {
253 ucontext_t uc;
254 FRAME_STRUCTURE *fp;
255 char *stk;
256 int fd;
257
258 /* if logging is disabled, just return */
259 pthread_mutex_lock(&log_lock);
260 if (log_fp == NULL) {
261 pthread_mutex_unlock(&log_lock);
262 return;
263 }
264
265 /* get the frame pointer from the current context and
266 pop our own frame */
267 getcontext(&uc);
268 fp = FRAMEP(uc.uc_mcontext.REG_SP);
269
270 if (fp == NULL) {
271 pthread_mutex_unlock(&log_lock);
272 return;
273 }
274 stk = (char *)NEXT_STACK(fp);
275 fp = NEXT_FRAME(fp);
276
277 /* if we've already logged this message here, don't make an entry */
278 if (check_exc_list(stk, (unsigned long)msg, stk, fp)) {
279 pthread_mutex_unlock(&log_lock);
280 return;
281 }
282
283 /* make an entry */
284 fd = fileno(log_fp);
285 write(fd, "fex_log_entry: ", 15);
286 write(fd, msg, strlen(msg));
287 write(fd, "\n", 1);
288 print_stack(fd, stk, fp);
289 pthread_mutex_unlock(&log_lock);
290 }
291
292 static const char *exception[FEX_NUM_EXC] = {
293 "inexact result",
294 "division by zero",
295 "underflow",
296 "overflow",
297 "invalid operation (0/0)",
298 "invalid operation (inf/inf)",
299 "invalid operation (inf-inf)",
300 "invalid operation (0*inf)",
301 "invalid operation (sqrt)",
302 "invalid operation (snan)",
303 "invalid operation (int)",
304 "invalid operation (cmp)"
305 };
306
307 void
__fex_mklog(ucontext_t * uap,char * addr,int f,enum fex_exception e,int m,void * p)308 __fex_mklog(ucontext_t *uap, char *addr, int f, enum fex_exception e,
309 int m, void *p)
310 {
311 FRAME_STRUCTURE *fp;
312 char *stk;
313 int fd;
314 char *line;
315
316 /* if logging is disabled, just return */
317 pthread_mutex_lock(&log_lock);
318 if (log_fp == NULL) {
319 pthread_mutex_unlock(&log_lock);
320 return;
321 }
322
323 /* get stack info */
324 stk = (char*)uap->uc_mcontext.REG_PC;
325 fp = FRAMEP(uap->uc_mcontext.REG_FP);
326
327
328 /* if the handling mode is the default and this exception's
329 flag is already raised, don't make an entry */
330 if (m == FEX_NONSTOP) {
331 switch (e) {
332 case fex_inexact:
333 if (f & FE_INEXACT) {
334 pthread_mutex_unlock(&log_lock);
335 return;
336 }
337 break;
338 case fex_underflow:
339 if (f & FE_UNDERFLOW) {
340 pthread_mutex_unlock(&log_lock);
341 return;
342 }
343 break;
344 case fex_overflow:
345 if (f & FE_OVERFLOW) {
346 pthread_mutex_unlock(&log_lock);
347 return;
348 }
349 break;
350 case fex_division:
351 if (f & FE_DIVBYZERO) {
352 pthread_mutex_unlock(&log_lock);
353 return;
354 }
355 break;
356 default:
357 if (f & FE_INVALID) {
358 pthread_mutex_unlock(&log_lock);
359 return;
360 }
361 break;
362 }
363 }
364
365 #if SUPPORT_STACK_WALKING
366 /* if we've already logged this exception at this address,
367 don't make an entry */
368 if (check_exc_list(addr, (unsigned long)e, stk, fp)) {
369 pthread_mutex_unlock(&log_lock);
370 return;
371 }
372 #endif
373
374 /* make an entry */
375 line = convert_address_to_symbol (addr);
376 fd = fileno(log_fp);
377 write(fd, "Floating point ", 15);
378 write(fd, exception[e], strlen(exception[e]));
379 write(fd, " at ", 4);
380 write(fd, line, strlen(line));
381 switch (m) {
382 case FEX_NONSTOP:
383 write(fd, ", nonstop mode\n", 15);
384 break;
385
386 case FEX_ABORT:
387 write(fd, ", abort\n", 8);
388 break;
389
390 case FEX_NOHANDLER:
391 if (p == (void *)SIG_DFL) {
392 write(fd, ", handler: SIG_DFL\n", 19);
393 break;
394 }
395 else if (p == (void *)SIG_IGN) {
396 write(fd, ", handler: SIG_IGN\n", 19);
397 break;
398 }
399 /* fall through*/
400 default:
401 write(fd, ", handler: ", 11);
402 line = convert_address_to_symbol (p);
403 write(fd, line, strlen(line));
404 write(fd, "\n", 1);
405 break;
406 }
407 #if SUPPORT_STACK_WALKING
408 print_stack(fd, stk, fp);
409 #else
410 print_previous_two_stack_addresses(fd, stk, fp);
411 #endif
412 pthread_mutex_unlock(&log_lock);
413 }
414