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