FORM  4.3
mallocprotect.h
1 #ifndef MALLOCPROTECT_H
2 #define MALLOCPROTECT_H
3 
10 /* #[ License : */
11 /*
12  * Copyright (C) 1984-2022 J.A.M. Vermaseren
13  * When using this file you are requested to refer to the publication
14  * J.A.M.Vermaseren "New features of FORM" math-ph/0010025
15  * This is considered a matter of courtesy as the development was paid
16  * for by FOM the Dutch physics granting agency and we would like to
17  * be able to track its scientific use to convince FOM of its value
18  * for the community.
19  *
20  * This file is part of FORM.
21  *
22  * FORM is free software: you can redistribute it and/or modify it under the
23  * terms of the GNU General Public License as published by the Free Software
24  * Foundation, either version 3 of the License, or (at your option) any later
25  * version.
26  *
27  * FORM is distributed in the hope that it will be useful, but WITHOUT ANY
28  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
29  * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
30  * details.
31  *
32  * You should have received a copy of the GNU General Public License along
33  * with FORM. If not, see <http://www.gnu.org/licenses/>.
34  */
35 /* #] License : */
36 
37 /*
38  #[ Documentation :
39 
40 The enhanced malloc debugger. It intercepts access (both write AND
41 read) to the memory outside the allocated chunk in the following
42 manner: it prints out (to the stderr) the information about the event
43 and goes to the spilock. The user is able to attach the debugger to
44 the running process, investigate the problem and continue evaluation.
45 
46 In case of error, the debugger will print to the stderr the following
47 information:
48 "***** PID: ###, Addr: ###, signal ### (code ###)"
49 "Attach gdb -p ### and set var loopForever=0 in a corresponding frame to continue"
50 or for TFORM:
51 "Attach gdb -p ### and set var loopForever=0 in a corresponding frame
52  of a thread with LPW id ### to continue"
53 
54 and go to the spilock. The user may attach gdb by the command
55 gdb -p ### and type "where" (in case of TFORM, the user should first
56 switch to the proper thread). Then investigation of the corresponding
57 frames should clarify the situation. In order to continue the program,
58 the user might set (in the corresponding frame of the corresponding
59 thread) the variable loopForever to 0. The debugger will try to remove
60 local problem lead to the exception.
61 
62 To activate the debugger, the macro MALLOCPROTECT should be
63 defined. There are three possible values:
64 #define MALLOCPROTECT -1 (any integer <0)
65 #define MALLOCPROTECT 0
66 #define MALLOCPROTECT 1 (any integer>0)
67 
68 If MALLOCPROTECT < 0, the debugger will intercept any access to the
69 memory before the allocated chunk, even one byte.
70 
71 If MALLOCPROTECT == 0, the debugger will intercept any access to the
72 memory before the allocated chunk, even one byte, and access to the
73 memory page next to the allocated one.
74 
75 If MALLOCPROTECT > 0, the debugger will intercept any access to the
76 memory after the allocated chunk, even one byte, and access to the
77 memory page before the allocated one.
78 
79 The original FORM malloc debugger is able to catch errors when the
80 allocated memory is freed improper, or if some small portion OUT of
81 the allocated chunk is written. This debugger is a complementary
82 one. It permits to catch situation when some small portion of memory
83 before or after the allocated chunk is written or even just read.
84 
85 The idea is to protect the beginning or the end of the allocated chunk
86 for any kind of access and install the SIGSEGV handler in order to
87 intercept the access to this memory. Moreover, the allocated memory is
88 always immediately returned to the system so if the user tries to
89 access the memory after it is freed then the handler is triggered,
90 also.
91 
92 The problem here is that we are able to allocate / protec only the
93 whole page. So the user has to run the debugger twice: one time with
94 the left alignment (#define MALLOCPROTECT <0), and then with the rigth
95 alignment (#define MALLOCPROTECT >0). During the first run the possible
96 errors like x[-1] will be catched, and the second run will manifest
97 reading over the allocated ares.
98 
99 The leftmost extra page is always allocated and mprotected. The size
100 of the allocated chunk is stored in the beginning of this page.
101 
102  #] Documentation :
103  #[ Includes :
104 */
105 
106 #include <stdio.h>
107 #include <stdlib.h>
108 #include <unistd.h>
109 #include <signal.h>
110 #include <sys/mman.h>
111 
112 #ifdef WITHPTHREADS
113 #ifdef LINUX
114 #include <sys/syscall.h>
115 #endif
116 #endif
117 
118 /*
119  #] Includes :
120 */
121 
122 static int pageSize=4096;/*the default value*/
123 
124 
125 /*
126  #[ segv_handler :
127 */
128 
129 /*The handler will be invoked on some signal (usually SIGSEGV). It
130 will print some diagnostics to stderr and hands up in infinite loop
131 waiting the user for debugging:*/
132 static void segv_handler(int sig, siginfo_t *sip, void *xxx) {
133  char *vadr;
134  char *errStr;
135  int actionBeforeExit=0;/* < 0 - unprotect, > 0 - map */
136  switch(sip->si_signo){/* All POSIX signals for which the field siginfo_t.si_addr is defined:*/
137  case SIGILL:
138  switch(sip->si_code){
139  case ILL_ILLOPC:
140  errStr="SIGILL: Illegal opcode";
141  break;
142  case ILL_ILLOPN:
143  errStr="SIGILL: Illegal operand";
144  break;
145  case ILL_ILLADR:
146  errStr="SIGILL: Illegal addressing mode";
147  break;
148  case ILL_ILLTRP:
149  errStr="SIGILL: Illegal trap";
150  break;
151  case ILL_PRVOPC:
152  errStr="SIGILL: Privileged opcode";
153  break;
154  case ILL_PRVREG:
155  errStr="SIGILL: Privileged register";
156  break;
157  case ILL_COPROC:
158  errStr="SIGILL: Coprocessor error";
159  break;
160  case ILL_BADSTK:
161  errStr="SIGILL: Internal stack error";
162  break;
163  default:
164  errStr="SIGILL: Unknown signal code";
165  }/*case SIGILL:*/
166  break;
167  case SIGFPE:
168  switch(sip->si_code){
169  case FPE_INTDIV:
170  errStr="SIGFPE: Integer divide-by-zero";
171  break;
172  case FPE_INTOVF:
173  errStr="SIGFPE: Integer overflow";
174  break;
175  case FPE_FLTDIV:
176  errStr="SIGFPE: Floating point divide-by-zero";
177  break;
178  case FPE_FLTOVF:
179  errStr="SIGFPE: Floating point overflow";
180  break;
181  case FPE_FLTUND:
182  errStr="SIGFPE: Floating point underflow";
183  break;
184  case FPE_FLTRES:
185  errStr="SIGFPE: Floating point inexact result";
186  break;
187  case FPE_FLTINV:
188  errStr="SIGFPE: Invalid floating point operation";
189  break;
190  case FPE_FLTSUB:
191  errStr="SIGFPE: Subscript out of range";
192  break;
193  default:
194  errStr="SIGFPE: Unknown signal code";
195  }/*switch(sip->si_code)*/
196  break;
197  case SIGSEGV:
198  switch(sip->si_code){
199  case SEGV_MAPERR:
200  errStr="SIGSEGV: Address not mapped";
201  actionBeforeExit = 1;
202  break;
203  case SEGV_ACCERR:
204  errStr="SIGSEGV: Invalid permissions";
205  actionBeforeExit = -1;
206  break;
207  default:
208  errStr="SIGSEGV: Unknown signal code";
209  }/*switch(sip->si_code)*/
210  break;
211  case SIGBUS:
212  switch(sip->si_code){
213  case BUS_ADRALN:
214  errStr="SIGBUS: Invalid address alignment";
215  break;
216  case BUS_ADRERR:
217  errStr="SIGBUS: Non-existent physical address";
218  break;
219  case BUS_OBJERR:
220  errStr="SIGBUS: Object-specific hardware error";
221  break;
222  default:
223  errStr="SIGBUS: Unknown signal code";
224  }/*switch(sip->si_code)*/
225  break;
226  default:
227  errStr="Unknown signal";
228  }/*switch(sip->si_signo)*/
229 
230  vadr = (caddr_t)sip->si_addr;
231  fprintf(stderr, "\n***** PID: %ld, Addr: %p, signal %s (code %d)\n",
232  (long)getpid(), vadr, errStr,sip->si_code);
233 
234  {/*Block*/
235  /*The process hangs up at this block.
236  Attach gdb to investigate and continue:
237  */
238  volatile int loopForever=1;
239  size_t alignedAdr=((size_t)vadr) & (~(pageSize-1));
240  fprintf(stderr, " Attach gdb -p %ld and set var loopForever=0 in a corresponding frame",
241  (long)getpid());
242 #ifdef CORRECTCODE
243 #ifdef WITHPTHREADS
244 #ifdef LINUX
245  fprintf(stderr, "\n of a thread with LPW id %ld",(long)syscall(SYS_gettid));
246 #else
247  /*If the compiler fails here, just remove the next line:*/
248  fprintf(stderr, "\n of thread with LPW id %ld",(long)gettid());
249 #endif
250 #endif
251 #endif
252  fprintf(stderr, " to continue\n");
253 
254 
255  while(loopForever)
256  sleep(1);
257 
258  if(actionBeforeExit<0)/*After changing loopForever=0, unprotect the page to continue:*/
259  mprotect((char*)alignedAdr, pageSize, PROT_READ | PROT_WRITE);
260  if(actionBeforeExit>0)/*After changing loopForever=0, map the page to continue:*/
261 /* mmap((void*)alignedAdr,pageSize,PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0); */
262  mmap((void*)alignedAdr,pageSize,PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, 0, 0);
263  }/*Block*/
264 }/*segv_handler*/
265 
266 /*
267  #] segv_handler :
268 */
269 
270 /*
271  #[ mprotectInit :
272 */
273 
274 static FORM_INLINE int mprotectInit(void)
275 {
276  struct sigaction sa;
277  pageSize = getpagesize();
278  sa.sa_sigaction = &segv_handler;
279 
280  sigemptyset(&sa.sa_mask);
281  sigaddset(&sa.sa_mask, SIGIO);
282  sigaddset(&sa.sa_mask, SIGALRM);
283 
284  sa.sa_flags = SA_SIGINFO;
285 
286  if (sigaction(SIGILL, &sa, NULL)) {
287  fprintf(stderr,"Error on assigning %s.\n","SIGILL");
288  return SIGILL;
289  }
290  if (sigaction(SIGFPE, &sa, NULL)) {
291  fprintf(stderr,"Error on assigning %s.\n","SIGFPE");
292  return SIGFPE;
293  }
294  if (sigaction(SIGSEGV, &sa, NULL)) {
295  fprintf(stderr,"Error on assigning %s.\n","SIGSEGV");
296  return SIGSEGV;
297  }
298  if (sigaction(SIGBUS, &sa, NULL)) {
299  fprintf(stderr,"Error on assigning %s.\n","SIGBUS");
300  return SIGBUS;
301  }
302  return 0;
303 }/*mprotectInit*/
304 
305 /*
306  #] mprotectInit :
307 */
308 
309 
310 /*
311  #[ mprotectMalloc :
312 */
313 
314 static void *mprotectMalloc(size_t theSize)
315 {
316 #if MALLOCPROTECT < 0
317  /*Only one side is protected*/
318  size_t nPages=1;
319 #else
320  /*Both sides are protected*/
321  size_t nPages=2;
322 #if MALLOCPROTECT > 0
323  /*will need the original theSize*/
324  size_t requestedSize=theSize;
325 #endif
326 #endif
327 
328  char *ret=NULL;
329  size_t *ptr;
330 
331 
332 
333  /*Align required size to the pagesize:*/
334  if(theSize % pageSize)
335  nPages++;
336  theSize= (theSize/pageSize+nPages)*pageSize;
337 
338 /* ret=(char*)mmap(0,theSize,PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0); */
339  ret=(char*)mmap(0,theSize,PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, 0, 0);
340  if(ret == MAP_FAILED)
341  return NULL;
342  ptr=(size_t *)ret;
343  *ptr=theSize;
344  if(mprotect(ret, pageSize, PROT_NONE))return NULL;
345 #if MALLOCPROTECT < 0
346  return ret+pageSize;
347 #else
348  if(mprotect(ret+(theSize-pageSize), pageSize, PROT_NONE))return NULL;
349 #if MALLOCPROTECT ==0
350  return ret+pageSize;
351 #endif
352 #endif
353  /*MALLOCPROTECT > 0, but we need conditional compilation
354  since the variable requestedSize:*/
355 #if MALLOCPROTECT > 0
356 
357  /*Potential problems with alignment if the requested size is not
358  a multiple of items. But no poblems on x86-64:*/
359  return ret+ (theSize-pageSize-requestedSize);
360 #endif
361 }/*mprotectMalloc*/
362 /*
363  #] mprotectMalloc :
364 */
365 
366 /*
367  #[ mprotectFree :
368 */
369 
370 static void mprotectFree(char *ptr)
371 {
372  size_t theSize;
373  if(ptr==NULL) return;
374 #if MALLOCPROTECT > 0
375  /*The memory block was moved to the right, find the left page boundary:*/
376  {/*Block*/
377  size_t alignedPtr=((size_t)ptr) & (~(pageSize-1));
378  ptr=(char*)alignedPtr;
379  }/*Block*/
380 #endif
381 
382  ptr-=pageSize;
383  mprotect(ptr, pageSize, PROT_READ);
384  theSize=*((size_t*)ptr);
385  munmap(ptr,theSize);
386 }/*mprotectFree*/
387 
388 /*
389  #] mprotectFree :
390 */
391 
392 #endif