1 /*  malloc_debug.c: attempt at malloc/free replacement for debugging
2     configure with --enable-debug to use it. Nowadays, valgrind
3     will often be more useful.
4 
5     When configured with --enable-debug, and run with the DEBUG_MALLOC
6     bit set this will log (in the debug log /tmp/rlwrap.debug) all
7     malloc() and free() calls made by rlwrap (but not those made on
8     behalf of rlwrap in library routines)
9 
10     At program exit this will log a list of all unfreed() memory
11     blocks on the heap
12 
13     if DEBUG_WITH_TIMESTAMPS is set in debug  , the timestamp of those
14     blocks (i.e. of their allocation) will be listed as well.
15 
16 
17 
18 */
19 
20 /*  This program is free software; you can redistribute it and/or modify
21     it under the terms of the GNU General Public License as published by
22     the Free Software Foundation; either version 2 of the License , or
23     (at your option) any later version.
24 
25     This program is distributed in the hope that it will be useful,
26     but WITHOUT ANY WARRANTY; without even the implied warranty of
27     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
28     GNU General Public License for more details.
29 
30     You should have received a copy of the GNU General Public License
31     along with this program; see the file COPYING.  If not, write to
32     the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
33 
34     You may contact the author by:
35     e-mail:  hanslub42@gmail.com
36 
37 */
38 
39 
40 
41 
42 #include "rlwrap.h"
43 
44 
45 #ifdef DEBUG
46 #  define USE_MALLOC_DEBUGGER
47 #  undef mymalloc
48 #  undef free
49 #endif
50 
51 #ifdef USE_MALLOC_DEBUGGER
52 
53 
54 
55 #define SLAVERY_SLOGAN "rlwrap is boss!"
56 #define FREEDOM_SLOGAN "free at last!"
57 #define SLOGAN_MAXLEN 20
58 #define TIMESTAMP_MAXLEN 30
59 
60 extern int debug;
61 
62 typedef RETSIGTYPE (*sighandler_t)(int);
63 
64 typedef struct freed_stamp
65 {
66   char magic[SLOGAN_MAXLEN];  /* magical string that tells us this something about this memory: malloced or freed? */
67   char *file;                 /* source file where we were malloced/freed */
68   int line;                   /* source line where we were malloced/freed */
69   int size;
70   char timestamp[TIMESTAMP_MAXLEN];
71   void *previous;             /* maintain a linked list of malloced/freed memory for post-mortem investigations*/
72 } *Freed_stamp;
73 
74 
75 static void* blocklist = 0;   /* start of linked list of allocated blocks. when freed, blocks stay on the list */
76 
77 static int memory_usage = 0;
78 
79 static char *offending_sourcefile;
80 static int offending_line;
81 sighandler_t old_segfault_handler;
82 
83 
84 /* local segfault handler, installed just before we dereference a pointer to test its writability */
85 static RETSIGTYPE
handle_segfault(int UNUSED (sig))86 handle_segfault(int UNUSED(sig))
87 {
88   fprintf(stderr, "free() called on bad (unallocated) memory at %s:%d\n", offending_sourcefile, offending_line);
89   exit(1);
90 }
91 
92 
93 
94 /* allocates chunk of memory including a freed_stamp, in which we write
95    line and file where we were alllocated, and a slogan to testify that
96    we have been allocated and not yet freed. returns the address past the stamp
97    If you think this memory will ever be freed by a normal free() use malloc_foreign instead
98 */
99 
100 void *
debug_malloc(size_t size,char * file,int line)101 debug_malloc(size_t size,  char *file, int line)
102 {
103   void *chunk;
104   Freed_stamp stamp;
105 
106   if (!(debug & DEBUG_MEMORY_MANAGEMENT))
107     return mymalloc(size);
108 
109   chunk = mymalloc(sizeof(struct freed_stamp) + size);
110   stamp = (Freed_stamp) chunk;
111   memory_usage += size;
112   DPRINTF4(DEBUG_MEMORY_MANAGEMENT, "malloc size: %d at %s:%d: (total usage now: %d)",  (int) size, file, line, memory_usage);
113   strncpy(stamp->magic, SLAVERY_SLOGAN, SLOGAN_MAXLEN);
114   stamp -> file = file;
115   stamp -> line = line;
116   stamp -> size = size;
117   if (debug & DEBUG_WITH_TIMESTAMPS)
118     timestamp(stamp -> timestamp, TIMESTAMP_MAXLEN);
119   else
120     stamp -> timestamp[0] = '\0';
121   stamp -> previous = blocklist;
122   blocklist = chunk;
123   return (char *) chunk + sizeof(struct freed_stamp);
124 }
125 
126 
127 
128 /* Verifies that ptr indeed points to memory allocaded by debug_malloc,
129    and has not yet been freed. Doesn't really free it, but marks it as freed
130    so that we easily notice double frees */
131 void
debug_free(void * ptr,char * file,int line)132 debug_free(void *ptr, char *file, int line)
133 {
134   Freed_stamp stamp;
135 
136   if (!(debug & DEBUG_MEMORY_MANAGEMENT)) {
137     free(ptr);
138     return;
139   }
140 
141   stamp = ((Freed_stamp) ptr) - 1;
142   offending_sourcefile = file; /* use static variables to communicate with signal handler */
143   offending_line = line;
144   old_segfault_handler = signal(SIGSEGV, &handle_segfault);
145   * (char *) ptr = 'x'; /* this, or the next statement  will provoke a segfault when address is not writable, i.e. in read-only memory or
146                            not in a mamory-mapped area */
147   if (strcmp(FREEDOM_SLOGAN, stamp -> magic) == 0) { /* Argghh! this memory has been freed before! */
148     fprintf(stderr, "free() called twice, at %s:%d: (on memory already freed at %s:%d)\n",
149             file, line, stamp->file, stamp->line);
150     exit(1);
151   } else if (strcmp(SLAVERY_SLOGAN, stamp -> magic) == 0) {
152     DPRINTF4(DEBUG_MEMORY_MANAGEMENT, "free() (called at %s:%d) of memory malloced at %s:%d", file, line, stamp->file, stamp->line);
153     strncpy(stamp->magic, FREEDOM_SLOGAN, SLOGAN_MAXLEN);
154     stamp -> file = file;
155     stamp -> line = line;
156     memory_usage -= stamp -> size;
157     signal(SIGSEGV, old_segfault_handler);
158     /* don't really free ptr */
159   } else {
160     fprintf(stderr, "free() called (at %s:%d) on unmalloced memory <%s>, or memory not malloced by debug_malloc()\n",
161             file, line, mangle_string_for_debug_log(ptr, 30));
162     close_logfile();
163     exit(1);
164   }
165 }
166 
167 /* this function calls free() directly, and should be used on memory that was malloc'ed outside our own jurisdiction,
168    i.e. not by debug_malloc(); */
free_foreign(void * ptr)169 void free_foreign(void *ptr) {
170   free(ptr);
171 }
172 
173 /* this function calls malloc() directly, and should be used on memory that could be freed  outside our own jurisdiction,
174    i.e. not by debug_free(); */
malloc_foreign(size_t size)175 void *malloc_foreign(size_t size) {
176   return malloc(size);
177 }
178 
179 /* sometimes we put in one structure objects that were malloced elsewhere and our own mymalloced objects.
180    we cannot free such a structure with free(), nor with free_foreign(). Solution: before using the "foreign" objects,
181    copy them to mymalloced memory and free them immediately
182    This function will be redefined to a NOP (i.e. just return its first argument) unless DEBUG is defined */
183 
copy_and_free_for_malloc_debug(void * ptr,size_t size)184 void *copy_and_free_for_malloc_debug(void *ptr, size_t size) {
185   void *copy;
186   if (ptr == NULL)
187     return NULL;
188   copy = debug_malloc(size,"foreign",0);
189   memcpy(copy, ptr, size);
190   free(ptr);
191   return copy;
192 }
193 
194 
copy_and_free_string_for_malloc_debug(char * str)195 char *copy_and_free_string_for_malloc_debug(char* str) {
196   if (str == NULL)
197     return NULL;
198   return copy_and_free_for_malloc_debug(str, strlen(str)+1);
199 }
200 
201 
202 /* this function logs all non-freed memory blocks (in order to hunt for memory leaks) */
203 /* blocklist = NULL, hence it is a no-op unless DEBUG_MALLOC has been defined */
debug_postmortem()204 void debug_postmortem() {
205   Freed_stamp p;
206   char *block;
207   DPRINTF0(DEBUG_MEMORY_MANAGEMENT,"Postmortem list of unfree memory blocks (most recently allocated first): ");
208   for (p = (Freed_stamp) blocklist; p; p =  p ->previous) {
209     if (strcmp(FREEDOM_SLOGAN, p -> magic) == 0)
210       continue;
211     else if (strcmp(SLAVERY_SLOGAN, p -> magic) == 0) {
212       block = (char *) p + sizeof(struct freed_stamp);
213       DPRINTF5(DEBUG_MEMORY_MANAGEMENT, "%d bytes malloced at %s %s:%d, contents: <%s>", p->size, p -> timestamp, p ->file, p ->line, M(block));
214     } else {
215       DPRINTF0(DEBUG_MEMORY_MANAGEMENT, "Hmmm,  unmalloced memory, or memory not malloced by debug_malloc()");
216     }
217   }
218 }
219 
220 
221 #endif /* def USE_MALLOC_DEBUGGER */
222