1 #define _GNU_SOURCE
2 
3 #ifndef __linux__
4 #error This only compiles under Linux
5 #endif
6 
7 #include <assert.h>
8 #include <ctype.h>
9 #include <string.h>
10 
11 #include <limits.h>
12 #include <signal.h>
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <sys/types.h>
16 #include <ucontext.h>
17 #include <unistd.h>
18 
19 #include "pocl_cache.h"
20 #include "pocl_cl.h"
21 #include "pocl_llvm.h"
22 #include "pocl_util.h"
23 
24 extern unsigned long buffer_c;
25 extern unsigned long svm_buffer_c;
26 extern unsigned long queue_c;
27 extern unsigned long context_c;
28 extern unsigned long image_c;
29 extern unsigned long kernel_c;
30 extern unsigned long program_c;
31 extern unsigned long sampler_c;
32 extern unsigned long uevent_c;
33 extern unsigned long event_c;
34 
35 static struct sigaction sigusr2_action, old_sigusr2_action;
36 
37 #define FORMATTED_ULONG_MAX_LEN 20
38 /* formats a number to a buffer, like snprintf which can't be called from
39  * signal handler returs an offset into "out" where the number starts */
40 static unsigned
format_int(unsigned long num,char * out)41 format_int (unsigned long num, char *out)
42 {
43   out[FORMATTED_ULONG_MAX_LEN] = 0x0A;
44   unsigned i = 0;
45   for (; i < FORMATTED_ULONG_MAX_LEN; ++i)
46     {
47       unsigned dig = num % 10;
48       num = num / 10;
49       out[FORMATTED_ULONG_MAX_LEN - 1 - i] = 48 + dig;
50       if (num == 0)
51         break;
52     }
53   return (FORMATTED_ULONG_MAX_LEN - 1 - i);
54 }
55 
56 #pragma GCC diagnostic push
57 #pragma GCC diagnostic ignored "-Wunused-result"
58 
59 #define SIGNAL_HANDLER_PRINTF(NUMBER, HEADER)                                 \
60   {                                                                           \
61     char store[FORMATTED_ULONG_MAX_LEN + 1];                                  \
62     unsigned offset;                                                          \
63     write (STDERR_FILENO, HEADER, strlen (HEADER));                           \
64     offset = format_int (NUMBER, store);                                      \
65     write (STDERR_FILENO, store + offset,                                     \
66            (FORMATTED_ULONG_MAX_LEN + 1 - offset));                           \
67   }
68 
69 static void
sigusr2_signal_handler(int signo,siginfo_t * si,void * data)70 sigusr2_signal_handler (int signo, siginfo_t *si, void *data)
71 {
72   if (signo == SIGUSR2)
73     {
74       SIGNAL_HANDLER_PRINTF (context_c, "Contexts: \n");
75       SIGNAL_HANDLER_PRINTF (queue_c, "Queues: \n");
76       SIGNAL_HANDLER_PRINTF (buffer_c, "Buffers: \n");
77       SIGNAL_HANDLER_PRINTF (image_c, "Images: \n");
78       SIGNAL_HANDLER_PRINTF (program_c, "Programs: \n");
79       SIGNAL_HANDLER_PRINTF (kernel_c, "Kernels: \n");
80       SIGNAL_HANDLER_PRINTF (sampler_c, "Samplers \n");
81       SIGNAL_HANDLER_PRINTF (uevent_c, "UserEvents: \n");
82       SIGNAL_HANDLER_PRINTF (event_c, "Events: \n");
83     }
84   else
85     (*old_sigusr2_action.sa_sigaction) (signo, si, data);
86 }
87 #pragma GCC diagnostic pop
88 
89 void
pocl_install_sigusr2_handler()90 pocl_install_sigusr2_handler ()
91 {
92   POCL_MSG_PRINT_GENERAL ("Installing SIGUSR2 handler...\n");
93 
94   sigusr2_action.sa_flags = SA_RESTART | SA_SIGINFO;
95   sigusr2_action.sa_sigaction = sigusr2_signal_handler;
96   int res = sigaction (SIGUSR2, &sigusr2_action, &old_sigusr2_action);
97   assert (res == 0);
98 }
99 
100 /* This ugly hack is required because:
101  *
102  * OpenCL 1.2 specification, 6.3 Operators :
103  *
104  * A divide by zero with integer types does not cause an exception
105  * but will result in an unspecified value. Division by zero for
106  * floating-point types will result in ±infinity or NaN as
107  * prescribed by the IEEE-754 standard.
108  *
109  * FPU exceptions are masked by default on x86 linux, but integer divide
110  * is not and there doesn't seem any sane way to mask it.
111  *
112  * This *might* be possible to fix with a LLVM pass (either check divisor
113  * for 0, or perhaps some vector extension has a suitable instruction), but
114  * it's likely to ruin the performance.
115  */
116 
117 #ifdef __x86_64__
118 
119 #define DIV_OPCODE_SIZE 1
120 #define DIV_OPCODE_MASK 0xf6
121 
122 /* F6 /6, F6 /7, F7 /6, F7 /7 */
123 #define DIV_OPCODE_1 0xf6
124 #define DIV_OPCODE_2 0xf7
125 #define DIV_MODRM_OPCODE_EXT_1 0x38 //  /7
126 #define DIV_MODRM_OPCODE_EXT_2 0x30 //  /6
127 
128 #define MODRM_SIZE 1
129 #define MODRM_MASK 0xC0
130 #define REG2_MASK 0x38
131 #define REG1_MASK 0x07
132 #define ADDR_MODE_INDIRECT_ONE_BYTE_OFFSET 0x40
133 #define ADDR_MODE_INDIRECT_FOUR_BYTE_OFFSET 0x80
134 #define ADDR_MODE_INDIRECT 0x0
135 #define ADDR_MODE_REGISTER_ONLY 0xC0
136 #define REG_SP 0x4
137 #define REG_BP 0x5
138 #define SIB_BYTE 1
139 #define IP_RELATIVE_INDEXING 4
140 
141 static struct sigaction sigfpe_action, old_sigfpe_action;
142 
143 static void
sigfpe_signal_handler(int signo,siginfo_t * si,void * data)144 sigfpe_signal_handler (int signo, siginfo_t *si, void *data)
145 {
146   ucontext_t *uc;
147   uc = (ucontext_t *)data;
148   unsigned char *eip = (unsigned char *)(uc->uc_mcontext.gregs[REG_RIP]);
149 
150   if ((signo == SIGFPE)
151       && ((si->si_code == FPE_INTDIV) || (si->si_code == FPE_INTOVF)))
152     {
153       /* Luckily for us, div-by-0 exceptions do NOT advance the IP register,
154        * so we have to disassemble the instruction (to know its length)
155        * and move IP past it. */
156       unsigned n = 0;
157 
158       /* skip all prefixes */
159       while ((n < 4) && ((eip[n] & DIV_OPCODE_MASK) != DIV_OPCODE_MASK))
160         ++n;
161 
162       /* too much prefixes = decoding failed */
163       if (n >= 4)
164         goto ORIGINAL_HANDLER;
165 
166       /* check opcode */
167       unsigned opcode = eip[n];
168       if ((opcode != DIV_OPCODE_1) && (opcode != DIV_OPCODE_2))
169         goto ORIGINAL_HANDLER;
170       n += DIV_OPCODE_SIZE;
171 
172       unsigned modrm = eip[n];
173       unsigned modmask = modrm & MODRM_MASK;
174       unsigned reg1mask = modrm & REG1_MASK;
175       unsigned reg2mask = modrm & REG2_MASK;
176       /* check opcode extension in ModR/M reg2 */
177       if ((reg2mask != DIV_MODRM_OPCODE_EXT_1)
178           && (reg2mask != DIV_MODRM_OPCODE_EXT_2))
179         goto ORIGINAL_HANDLER;
180       n += MODRM_SIZE;
181 
182       /* handle immediates/registers */
183       if (modmask == ADDR_MODE_INDIRECT_ONE_BYTE_OFFSET)
184         n += 1;
185       if (modmask == ADDR_MODE_INDIRECT_FOUR_BYTE_OFFSET)
186         n += 4;
187       if (modmask == ADDR_MODE_INDIRECT)
188         n += 0;
189       if (modmask != ADDR_MODE_REGISTER_ONLY)
190         {
191           if (reg1mask == REG_SP)
192             n += SIB_BYTE;
193           if (reg1mask == REG_BP)
194             n += IP_RELATIVE_INDEXING;
195         }
196 
197       uc->uc_mcontext.gregs[REG_RIP] += n;
198       return;
199     }
200   else
201     {
202     ORIGINAL_HANDLER:
203       (*old_sigfpe_action.sa_sigaction) (signo, si, data);
204     }
205 }
206 
207 #endif
208 
209 void
pocl_install_sigfpe_handler()210 pocl_install_sigfpe_handler ()
211 {
212 #ifdef __x86_64__
213 
214 #ifdef ENABLE_LLVM
215   /* This is required to force LLVM to register its signal
216    * handlers, before pocl registers its own SIGFPE handler.
217    * LLVM otherwise calls this via
218    *    pocl_llvm_build_program ->
219    *    clang::PrintPreprocessedAction ->
220    *    CreateOutputFile -> RemoveFileOnSignal
221    * Registering our handlers before LLVM creates its sigaltstack
222    * leads to interesting crashes & bugs later.
223    */
224   char random_empty_file[POCL_FILENAME_LENGTH];
225   pocl_cache_tempname (random_empty_file, NULL, NULL);
226   pocl_llvm_remove_file_on_signal (random_empty_file);
227 #endif
228 
229   POCL_MSG_PRINT_GENERAL ("Installing SIGFPE handler...\n");
230 
231   sigfpe_action.sa_flags = SA_RESTART | SA_SIGINFO;
232   sigfpe_action.sa_sigaction = sigfpe_signal_handler;
233   int res = sigaction (SIGFPE, &sigfpe_action, &old_sigfpe_action);
234   assert (res == 0);
235 #endif
236 }
237