]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | /* |
2 | * Copyright 1988, 1989 Hans-J. Boehm, Alan J. Demers | |
3 | * Copyright (c) 1991-1995 by Xerox Corporation. All rights reserved. | |
4 | * Copyright (c) 1997 by Silicon Graphics. All rights reserved. | |
5 | * Copyright (c) 1999-2004 Hewlett-Packard Development Company, L.P. | |
6 | * Copyright (C) 2007 Free Software Foundation, Inc | |
7 | * | |
8 | * THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED | |
9 | * OR IMPLIED. ANY USE IS AT YOUR OWN RISK. | |
10 | * | |
11 | * Permission is hereby granted to use or copy this program | |
12 | * for any purpose, provided the above notices are retained on all copies. | |
13 | * Permission to modify the code and to distribute modified code is granted, | |
14 | * provided the above notices are retained, and a notice that the code was | |
15 | * modified is included with the above copyright notice. | |
16 | */ | |
17 | ||
18 | #include <errno.h> | |
19 | #include <string.h> | |
20 | #include "private/dbg_mlc.h" | |
21 | ||
22 | void GC_default_print_heap_obj_proc(); | |
23 | GC_API void GC_register_finalizer_no_order | |
24 | (void * obj, GC_finalization_proc fn, void * cd, | |
25 | GC_finalization_proc *ofn, void * *ocd); | |
26 | ||
27 | ||
28 | #ifndef SHORT_DBG_HDRS | |
29 | /* Check whether object with base pointer p has debugging info */ | |
30 | /* p is assumed to point to a legitimate object in our part */ | |
31 | /* of the heap. */ | |
32 | /* This excludes the check as to whether the back pointer is */ | |
33 | /* odd, which is added by the GC_HAS_DEBUG_INFO macro. */ | |
34 | /* Note that if DBG_HDRS_ALL is set, uncollectable objects */ | |
35 | /* on free lists may not have debug information set. Thus it's */ | |
36 | /* not always safe to return TRUE, even if the client does */ | |
37 | /* its part. */ | |
38 | GC_bool GC_has_other_debug_info(ptr_t p) | |
39 | { | |
40 | register oh * ohdr = (oh *)p; | |
41 | register ptr_t body = (ptr_t)(ohdr + 1); | |
42 | register word sz = GC_size((ptr_t) ohdr); | |
43 | ||
44 | if (HBLKPTR((ptr_t)ohdr) != HBLKPTR((ptr_t)body) | |
45 | || sz < DEBUG_BYTES + EXTRA_BYTES) { | |
46 | return(FALSE); | |
47 | } | |
48 | if (ohdr -> oh_sz == sz) { | |
49 | /* Object may have had debug info, but has been deallocated */ | |
50 | return(FALSE); | |
51 | } | |
52 | if (ohdr -> oh_sf == (START_FLAG ^ (word)body)) return(TRUE); | |
53 | if (((word *)ohdr)[BYTES_TO_WORDS(sz)-1] == (END_FLAG ^ (word)body)) { | |
54 | return(TRUE); | |
55 | } | |
56 | return(FALSE); | |
57 | } | |
58 | #endif | |
59 | ||
60 | #ifdef KEEP_BACK_PTRS | |
61 | ||
62 | # include <stdlib.h> | |
63 | ||
64 | # if defined(LINUX) || defined(SOLARIS) \ | |
65 | || defined(HPUX) || defined(IRIX5) || defined(OSF1) | |
66 | # define RANDOM() random() | |
67 | # else | |
68 | # define RANDOM() (long)rand() | |
69 | # endif | |
70 | ||
71 | /* Store back pointer to source in dest, if that appears to be possible. */ | |
72 | /* This is not completely safe, since we may mistakenly conclude that */ | |
73 | /* dest has a debugging wrapper. But the error probability is very */ | |
74 | /* small, and this shouldn't be used in production code. */ | |
75 | /* We assume that dest is the real base pointer. Source will usually */ | |
76 | /* be a pointer to the interior of an object. */ | |
77 | void GC_store_back_pointer(ptr_t source, ptr_t dest) | |
78 | { | |
79 | if (GC_HAS_DEBUG_INFO(dest)) { | |
80 | ((oh *)dest) -> oh_back_ptr = HIDE_BACK_PTR(source); | |
81 | } | |
82 | } | |
83 | ||
84 | void GC_marked_for_finalization(ptr_t dest) { | |
85 | GC_store_back_pointer(MARKED_FOR_FINALIZATION, dest); | |
86 | } | |
87 | ||
88 | /* Store information about the object referencing dest in *base_p */ | |
89 | /* and *offset_p. */ | |
90 | /* source is root ==> *base_p = address, *offset_p = 0 */ | |
91 | /* source is heap object ==> *base_p != 0, *offset_p = offset */ | |
92 | /* Returns 1 on success, 0 if source couldn't be determined. */ | |
93 | /* Dest can be any address within a heap object. */ | |
94 | GC_ref_kind GC_get_back_ptr_info(void *dest, void **base_p, size_t *offset_p) | |
95 | { | |
96 | oh * hdr = (oh *)GC_base(dest); | |
97 | ptr_t bp; | |
98 | ptr_t bp_base; | |
99 | if (!GC_HAS_DEBUG_INFO((ptr_t) hdr)) return GC_NO_SPACE; | |
100 | bp = REVEAL_POINTER(hdr -> oh_back_ptr); | |
101 | if (MARKED_FOR_FINALIZATION == bp) return GC_FINALIZER_REFD; | |
102 | if (MARKED_FROM_REGISTER == bp) return GC_REFD_FROM_REG; | |
103 | if (NOT_MARKED == bp) return GC_UNREFERENCED; | |
104 | # if ALIGNMENT == 1 | |
105 | /* Heuristically try to fix off by 1 errors we introduced by */ | |
106 | /* insisting on even addresses. */ | |
107 | { | |
108 | ptr_t alternate_ptr = bp + 1; | |
109 | ptr_t target = *(ptr_t *)bp; | |
110 | ptr_t alternate_target = *(ptr_t *)alternate_ptr; | |
111 | ||
112 | if (alternate_target >= GC_least_plausible_heap_addr | |
113 | && alternate_target <= GC_greatest_plausible_heap_addr | |
114 | && (target < GC_least_plausible_heap_addr | |
115 | || target > GC_greatest_plausible_heap_addr)) { | |
116 | bp = alternate_ptr; | |
117 | } | |
118 | } | |
119 | # endif | |
120 | bp_base = GC_base(bp); | |
121 | if (0 == bp_base) { | |
122 | *base_p = bp; | |
123 | *offset_p = 0; | |
124 | return GC_REFD_FROM_ROOT; | |
125 | } else { | |
126 | if (GC_HAS_DEBUG_INFO(bp_base)) bp_base += sizeof(oh); | |
127 | *base_p = bp_base; | |
128 | *offset_p = bp - bp_base; | |
129 | return GC_REFD_FROM_HEAP; | |
130 | } | |
131 | } | |
132 | ||
133 | /* Generate a random heap address. */ | |
134 | /* The resulting address is in the heap, but */ | |
135 | /* not necessarily inside a valid object. */ | |
136 | void *GC_generate_random_heap_address(void) | |
137 | { | |
138 | int i; | |
139 | long heap_offset = RANDOM(); | |
140 | if (GC_heapsize > RAND_MAX) { | |
141 | heap_offset *= RAND_MAX; | |
142 | heap_offset += RANDOM(); | |
143 | } | |
144 | heap_offset %= GC_heapsize; | |
145 | /* This doesn't yield a uniform distribution, especially if */ | |
146 | /* e.g. RAND_MAX = 1.5* GC_heapsize. But for typical cases, */ | |
147 | /* it's not too bad. */ | |
148 | for (i = 0; i < GC_n_heap_sects; ++ i) { | |
149 | size_t size = GC_heap_sects[i].hs_bytes; | |
150 | if (heap_offset < size) { | |
151 | return GC_heap_sects[i].hs_start + heap_offset; | |
152 | } else { | |
153 | heap_offset -= size; | |
154 | } | |
155 | } | |
156 | ABORT("GC_generate_random_heap_address: size inconsistency"); | |
157 | /*NOTREACHED*/ | |
158 | return 0; | |
159 | } | |
160 | ||
161 | /* Generate a random address inside a valid marked heap object. */ | |
162 | void *GC_generate_random_valid_address(void) | |
163 | { | |
164 | ptr_t result; | |
165 | ptr_t base; | |
166 | for (;;) { | |
167 | result = GC_generate_random_heap_address(); | |
168 | base = GC_base(result); | |
169 | if (0 == base) continue; | |
170 | if (!GC_is_marked(base)) continue; | |
171 | return result; | |
172 | } | |
173 | } | |
174 | ||
175 | /* Print back trace for p */ | |
176 | void GC_print_backtrace(void *p) | |
177 | { | |
178 | void *current = p; | |
179 | int i; | |
180 | GC_ref_kind source; | |
181 | size_t offset; | |
182 | void *base; | |
183 | ||
184 | GC_print_heap_obj(GC_base(current)); | |
185 | GC_err_printf("\n"); | |
186 | for (i = 0; ; ++i) { | |
187 | source = GC_get_back_ptr_info(current, &base, &offset); | |
188 | if (GC_UNREFERENCED == source) { | |
189 | GC_err_printf("Reference could not be found\n"); | |
190 | goto out; | |
191 | } | |
192 | if (GC_NO_SPACE == source) { | |
193 | GC_err_printf("No debug info in object: Can't find reference\n"); | |
194 | goto out; | |
195 | } | |
196 | GC_err_printf("Reachable via %d levels of pointers from ", | |
197 | (unsigned long)i); | |
198 | switch(source) { | |
199 | case GC_REFD_FROM_ROOT: | |
200 | GC_err_printf("root at %p\n\n", base); | |
201 | goto out; | |
202 | case GC_REFD_FROM_REG: | |
203 | GC_err_printf("root in register\n\n"); | |
204 | goto out; | |
205 | case GC_FINALIZER_REFD: | |
206 | GC_err_printf("list of finalizable objects\n\n"); | |
207 | goto out; | |
208 | case GC_REFD_FROM_HEAP: | |
209 | GC_err_printf("offset %ld in object:\n", (unsigned long)offset); | |
210 | /* Take GC_base(base) to get real base, i.e. header. */ | |
211 | GC_print_heap_obj(GC_base(base)); | |
212 | GC_err_printf("\n"); | |
213 | break; | |
214 | } | |
215 | current = base; | |
216 | } | |
217 | out:; | |
218 | } | |
219 | ||
220 | /* Force a garbage collection and generate a backtrace from a */ | |
221 | /* random heap address. */ | |
222 | void GC_generate_random_backtrace_no_gc(void) | |
223 | { | |
224 | void * current; | |
225 | current = GC_generate_random_valid_address(); | |
226 | GC_printf("\n****Chose address %p in object\n", current); | |
227 | GC_print_backtrace(current); | |
228 | } | |
229 | ||
230 | void GC_generate_random_backtrace(void) | |
231 | { | |
232 | GC_gcollect(); | |
233 | GC_generate_random_backtrace_no_gc(); | |
234 | } | |
235 | ||
236 | #endif /* KEEP_BACK_PTRS */ | |
237 | ||
238 | # define CROSSES_HBLK(p, sz) \ | |
239 | (((word)(p + sizeof(oh) + sz - 1) ^ (word)p) >= HBLKSIZE) | |
240 | /* Store debugging info into p. Return displaced pointer. */ | |
241 | /* Assumes we don't hold allocation lock. */ | |
242 | ptr_t GC_store_debug_info(ptr_t p, word sz, const char *string, word integer) | |
243 | { | |
244 | register word * result = (word *)((oh *)p + 1); | |
245 | DCL_LOCK_STATE; | |
246 | ||
247 | /* There is some argument that we should dissble signals here. */ | |
248 | /* But that's expensive. And this way things should only appear */ | |
249 | /* inconsistent while we're in the handler. */ | |
250 | LOCK(); | |
251 | GC_ASSERT(GC_size(p) >= sizeof(oh) + sz); | |
252 | GC_ASSERT(!(SMALL_OBJ(sz) && CROSSES_HBLK(p, sz))); | |
253 | # ifdef KEEP_BACK_PTRS | |
254 | ((oh *)p) -> oh_back_ptr = HIDE_BACK_PTR(NOT_MARKED); | |
255 | # endif | |
256 | # ifdef MAKE_BACK_GRAPH | |
257 | ((oh *)p) -> oh_bg_ptr = HIDE_BACK_PTR((ptr_t)0); | |
258 | # endif | |
259 | ((oh *)p) -> oh_string = string; | |
260 | ((oh *)p) -> oh_int = integer; | |
261 | # ifndef SHORT_DBG_HDRS | |
262 | ((oh *)p) -> oh_sz = sz; | |
263 | ((oh *)p) -> oh_sf = START_FLAG ^ (word)result; | |
264 | ((word *)p)[BYTES_TO_WORDS(GC_size(p))-1] = | |
265 | result[SIMPLE_ROUNDED_UP_WORDS(sz)] = END_FLAG ^ (word)result; | |
266 | # endif | |
267 | UNLOCK(); | |
268 | return((ptr_t)result); | |
269 | } | |
270 | ||
271 | #ifdef DBG_HDRS_ALL | |
272 | /* Store debugging info into p. Return displaced pointer. */ | |
273 | /* This version assumes we do hold the allocation lock. */ | |
274 | ptr_t GC_store_debug_info_inner(ptr_t p, word sz, char *string, word integer) | |
275 | { | |
276 | register word * result = (word *)((oh *)p + 1); | |
277 | ||
278 | /* There is some argument that we should disable signals here. */ | |
279 | /* But that's expensive. And this way things should only appear */ | |
280 | /* inconsistent while we're in the handler. */ | |
281 | GC_ASSERT(GC_size(p) >= sizeof(oh) + sz); | |
282 | GC_ASSERT(!(SMALL_OBJ(sz) && CROSSES_HBLK(p, sz))); | |
283 | # ifdef KEEP_BACK_PTRS | |
284 | ((oh *)p) -> oh_back_ptr = HIDE_BACK_PTR(NOT_MARKED); | |
285 | # endif | |
286 | # ifdef MAKE_BACK_GRAPH | |
287 | ((oh *)p) -> oh_bg_ptr = HIDE_BACK_PTR((ptr_t)0); | |
288 | # endif | |
289 | ((oh *)p) -> oh_string = string; | |
290 | ((oh *)p) -> oh_int = integer; | |
291 | # ifndef SHORT_DBG_HDRS | |
292 | ((oh *)p) -> oh_sz = sz; | |
293 | ((oh *)p) -> oh_sf = START_FLAG ^ (word)result; | |
294 | ((word *)p)[BYTES_TO_WORDS(GC_size(p))-1] = | |
295 | result[SIMPLE_ROUNDED_UP_WORDS(sz)] = END_FLAG ^ (word)result; | |
296 | # endif | |
297 | return((ptr_t)result); | |
298 | } | |
299 | #endif | |
300 | ||
301 | #ifndef SHORT_DBG_HDRS | |
302 | /* Check the object with debugging info at ohdr */ | |
303 | /* return NIL if it's OK. Else return clobbered */ | |
304 | /* address. */ | |
305 | ptr_t GC_check_annotated_obj(oh *ohdr) | |
306 | { | |
307 | register ptr_t body = (ptr_t)(ohdr + 1); | |
308 | register word gc_sz = GC_size((ptr_t)ohdr); | |
309 | if (ohdr -> oh_sz + DEBUG_BYTES > gc_sz) { | |
310 | return((ptr_t)(&(ohdr -> oh_sz))); | |
311 | } | |
312 | if (ohdr -> oh_sf != (START_FLAG ^ (word)body)) { | |
313 | return((ptr_t)(&(ohdr -> oh_sf))); | |
314 | } | |
315 | if (((word *)ohdr)[BYTES_TO_WORDS(gc_sz)-1] != (END_FLAG ^ (word)body)) { | |
316 | return((ptr_t)((word *)ohdr + BYTES_TO_WORDS(gc_sz)-1)); | |
317 | } | |
318 | if (((word *)body)[SIMPLE_ROUNDED_UP_WORDS(ohdr -> oh_sz)] | |
319 | != (END_FLAG ^ (word)body)) { | |
320 | return((ptr_t)((word *)body + SIMPLE_ROUNDED_UP_WORDS(ohdr -> oh_sz))); | |
321 | } | |
322 | return(0); | |
323 | } | |
324 | #endif /* !SHORT_DBG_HDRS */ | |
325 | ||
326 | static GC_describe_type_fn GC_describe_type_fns[MAXOBJKINDS] = {0}; | |
327 | ||
328 | void GC_register_describe_type_fn(int kind, GC_describe_type_fn fn) | |
329 | { | |
330 | GC_describe_type_fns[kind] = fn; | |
331 | } | |
332 | ||
333 | /* Print a type description for the object whose client-visible address */ | |
334 | /* is p. */ | |
335 | void GC_print_type(ptr_t p) | |
336 | { | |
337 | hdr * hhdr = GC_find_header(p); | |
338 | char buffer[GC_TYPE_DESCR_LEN + 1]; | |
339 | int kind = hhdr -> hb_obj_kind; | |
340 | ||
341 | if (0 != GC_describe_type_fns[kind] && GC_is_marked(GC_base(p))) { | |
342 | /* This should preclude free list objects except with */ | |
343 | /* thread-local allocation. */ | |
344 | buffer[GC_TYPE_DESCR_LEN] = 0; | |
345 | (GC_describe_type_fns[kind])(p, buffer); | |
346 | GC_ASSERT(buffer[GC_TYPE_DESCR_LEN] == 0); | |
347 | GC_err_puts(buffer); | |
348 | } else { | |
349 | switch(kind) { | |
350 | case PTRFREE: | |
351 | GC_err_puts("PTRFREE"); | |
352 | break; | |
353 | case NORMAL: | |
354 | GC_err_puts("NORMAL"); | |
355 | break; | |
356 | case UNCOLLECTABLE: | |
357 | GC_err_puts("UNCOLLECTABLE"); | |
358 | break; | |
359 | # ifdef ATOMIC_UNCOLLECTABLE | |
360 | case AUNCOLLECTABLE: | |
361 | GC_err_puts("ATOMIC UNCOLLECTABLE"); | |
362 | break; | |
363 | # endif | |
364 | case STUBBORN: | |
365 | GC_err_puts("STUBBORN"); | |
366 | break; | |
367 | default: | |
368 | GC_err_printf("kind %d, descr 0x%lx", kind, | |
369 | (unsigned long)(hhdr -> hb_descr)); | |
370 | } | |
371 | } | |
372 | } | |
373 | ||
374 | ||
375 | ||
376 | void GC_print_obj(ptr_t p) | |
377 | { | |
378 | register oh * ohdr = (oh *)GC_base(p); | |
379 | ||
380 | GC_ASSERT(I_DONT_HOLD_LOCK()); | |
381 | GC_err_printf("%p (", ((ptr_t)ohdr + sizeof(oh))); | |
382 | GC_err_puts(ohdr -> oh_string); | |
383 | # ifdef SHORT_DBG_HDRS | |
384 | GC_err_printf(":%ld, ", (unsigned long)(ohdr -> oh_int)); | |
385 | # else | |
386 | GC_err_printf(":%ld, sz=%ld, ", (unsigned long)(ohdr -> oh_int), | |
387 | (unsigned long)(ohdr -> oh_sz)); | |
388 | # endif | |
389 | GC_print_type((ptr_t)(ohdr + 1)); | |
390 | GC_err_puts(")\n"); | |
391 | PRINT_CALL_CHAIN(ohdr); | |
392 | } | |
393 | ||
394 | void GC_debug_print_heap_obj_proc(ptr_t p) | |
395 | { | |
396 | GC_ASSERT(I_DONT_HOLD_LOCK()); | |
397 | if (GC_HAS_DEBUG_INFO(p)) { | |
398 | GC_print_obj(p); | |
399 | } else { | |
400 | GC_default_print_heap_obj_proc(p); | |
401 | } | |
402 | } | |
403 | ||
404 | #ifndef SHORT_DBG_HDRS | |
405 | void GC_print_smashed_obj(ptr_t p, ptr_t clobbered_addr) | |
406 | { | |
407 | register oh * ohdr = (oh *)GC_base(p); | |
408 | ||
409 | GC_ASSERT(I_DONT_HOLD_LOCK()); | |
410 | GC_err_printf("%p in object at %p(", clobbered_addr, p); | |
411 | if (clobbered_addr <= (ptr_t)(&(ohdr -> oh_sz)) | |
412 | || ohdr -> oh_string == 0) { | |
413 | GC_err_printf("<smashed>, appr. sz = %ld)\n", | |
414 | (GC_size((ptr_t)ohdr) - DEBUG_BYTES)); | |
415 | } else { | |
416 | if (ohdr -> oh_string[0] == '\0') { | |
417 | GC_err_puts("EMPTY(smashed?)"); | |
418 | } else { | |
419 | GC_err_puts(ohdr -> oh_string); | |
420 | } | |
421 | GC_err_printf(":%ld, sz=%ld)\n", (unsigned long)(ohdr -> oh_int), | |
422 | (unsigned long)(ohdr -> oh_sz)); | |
423 | PRINT_CALL_CHAIN(ohdr); | |
424 | } | |
425 | } | |
426 | #endif | |
427 | ||
428 | void GC_check_heap_proc (void); | |
429 | ||
430 | void GC_print_all_smashed_proc (void); | |
431 | ||
432 | void GC_do_nothing(void) {} | |
433 | ||
434 | void GC_start_debugging(void) | |
435 | { | |
436 | # ifndef SHORT_DBG_HDRS | |
437 | GC_check_heap = GC_check_heap_proc; | |
438 | GC_print_all_smashed = GC_print_all_smashed_proc; | |
439 | # else | |
440 | GC_check_heap = GC_do_nothing; | |
441 | GC_print_all_smashed = GC_do_nothing; | |
442 | # endif | |
443 | GC_print_heap_obj = GC_debug_print_heap_obj_proc; | |
444 | GC_debugging_started = TRUE; | |
445 | GC_register_displacement((word)sizeof(oh)); | |
446 | } | |
447 | ||
448 | size_t GC_debug_header_size = sizeof(oh); | |
449 | ||
450 | void GC_debug_register_displacement(size_t offset) | |
451 | { | |
452 | GC_register_displacement(offset); | |
453 | GC_register_displacement((word)sizeof(oh) + offset); | |
454 | } | |
455 | ||
456 | void * GC_debug_malloc(size_t lb, GC_EXTRA_PARAMS) | |
457 | { | |
458 | void * result = GC_malloc(lb + DEBUG_BYTES); | |
459 | ||
460 | if (result == 0) { | |
461 | GC_err_printf("GC_debug_malloc(%lu) returning NIL (", | |
462 | (unsigned long) lb); | |
463 | GC_err_puts(s); | |
464 | GC_err_printf(":%ld)\n", (unsigned long)i); | |
465 | return(0); | |
466 | } | |
467 | if (!GC_debugging_started) { | |
468 | GC_start_debugging(); | |
469 | } | |
470 | ADD_CALL_CHAIN(result, ra); | |
471 | return (GC_store_debug_info(result, (word)lb, s, (word)i)); | |
472 | } | |
473 | ||
474 | void * GC_debug_malloc_ignore_off_page(size_t lb, GC_EXTRA_PARAMS) | |
475 | { | |
476 | void * result = GC_malloc_ignore_off_page(lb + DEBUG_BYTES); | |
477 | ||
478 | if (result == 0) { | |
479 | GC_err_printf("GC_debug_malloc_ignore_off_page(%lu) returning NIL (", | |
480 | (unsigned long) lb); | |
481 | GC_err_puts(s); | |
482 | GC_err_printf(":%lu)\n", (unsigned long)i); | |
483 | return(0); | |
484 | } | |
485 | if (!GC_debugging_started) { | |
486 | GC_start_debugging(); | |
487 | } | |
488 | ADD_CALL_CHAIN(result, ra); | |
489 | return (GC_store_debug_info(result, (word)lb, s, (word)i)); | |
490 | } | |
491 | ||
492 | void * GC_debug_malloc_atomic_ignore_off_page(size_t lb, GC_EXTRA_PARAMS) | |
493 | { | |
494 | void * result = GC_malloc_atomic_ignore_off_page(lb + DEBUG_BYTES); | |
495 | ||
496 | if (result == 0) { | |
497 | GC_err_printf("GC_debug_malloc_atomic_ignore_off_page(%lu)" | |
498 | " returning NIL (", (unsigned long) lb); | |
499 | GC_err_puts(s); | |
500 | GC_err_printf(":%lu)\n", (unsigned long)i); | |
501 | return(0); | |
502 | } | |
503 | if (!GC_debugging_started) { | |
504 | GC_start_debugging(); | |
505 | } | |
506 | ADD_CALL_CHAIN(result, ra); | |
507 | return (GC_store_debug_info(result, (word)lb, s, (word)i)); | |
508 | } | |
509 | ||
510 | # ifdef DBG_HDRS_ALL | |
511 | /* | |
512 | * An allocation function for internal use. | |
513 | * Normally internally allocated objects do not have debug information. | |
514 | * But in this case, we need to make sure that all objects have debug | |
515 | * headers. | |
516 | * We assume debugging was started in collector initialization, | |
517 | * and we already hold the GC lock. | |
518 | */ | |
519 | void * GC_debug_generic_malloc_inner(size_t lb, int k) | |
520 | { | |
521 | void * result = GC_generic_malloc_inner(lb + DEBUG_BYTES, k); | |
522 | ||
523 | if (result == 0) { | |
524 | GC_err_printf("GC internal allocation (%lu bytes) returning NIL\n", | |
525 | (unsigned long) lb); | |
526 | return(0); | |
527 | } | |
528 | ADD_CALL_CHAIN(result, GC_RETURN_ADDR); | |
529 | return (GC_store_debug_info_inner(result, (word)lb, "INTERNAL", (word)0)); | |
530 | } | |
531 | ||
532 | void * GC_debug_generic_malloc_inner_ignore_off_page(size_t lb, int k) | |
533 | { | |
534 | void * result = GC_generic_malloc_inner_ignore_off_page( | |
535 | lb + DEBUG_BYTES, k); | |
536 | ||
537 | if (result == 0) { | |
538 | GC_err_printf("GC internal allocation (%lu bytes) returning NIL\n", | |
539 | (unsigned long) lb); | |
540 | return(0); | |
541 | } | |
542 | ADD_CALL_CHAIN(result, GC_RETURN_ADDR); | |
543 | return (GC_store_debug_info_inner(result, (word)lb, "INTERNAL", (word)0)); | |
544 | } | |
545 | # endif | |
546 | ||
547 | #ifdef STUBBORN_ALLOC | |
548 | void * GC_debug_malloc_stubborn(size_t lb, GC_EXTRA_PARAMS) | |
549 | { | |
550 | void * result = GC_malloc_stubborn(lb + DEBUG_BYTES); | |
551 | ||
552 | if (result == 0) { | |
553 | GC_err_printf("GC_debug_malloc(%lu) returning NIL (", | |
554 | (unsigned long) lb); | |
555 | GC_err_puts(s); | |
556 | GC_err_printf(":%lu)\n", (unsigned long)i); | |
557 | return(0); | |
558 | } | |
559 | if (!GC_debugging_started) { | |
560 | GC_start_debugging(); | |
561 | } | |
562 | ADD_CALL_CHAIN(result, ra); | |
563 | return (GC_store_debug_info(result, (word)lb, s, (word)i)); | |
564 | } | |
565 | ||
566 | void GC_debug_change_stubborn(void *p) | |
567 | { | |
568 | void * q = GC_base(p); | |
569 | hdr * hhdr; | |
570 | ||
571 | if (q == 0) { | |
572 | GC_err_printf("Bad argument: %p to GC_debug_change_stubborn\n", p); | |
573 | ABORT("GC_debug_change_stubborn: bad arg"); | |
574 | } | |
575 | hhdr = HDR(q); | |
576 | if (hhdr -> hb_obj_kind != STUBBORN) { | |
577 | GC_err_printf("GC_debug_change_stubborn arg not stubborn: %p\n", p); | |
578 | ABORT("GC_debug_change_stubborn: arg not stubborn"); | |
579 | } | |
580 | GC_change_stubborn(q); | |
581 | } | |
582 | ||
583 | void GC_debug_end_stubborn_change(void *p) | |
584 | { | |
585 | register void * q = GC_base(p); | |
586 | register hdr * hhdr; | |
587 | ||
588 | if (q == 0) { | |
589 | GC_err_printf("Bad argument: %p to GC_debug_end_stubborn_change\n", p); | |
590 | ABORT("GC_debug_end_stubborn_change: bad arg"); | |
591 | } | |
592 | hhdr = HDR(q); | |
593 | if (hhdr -> hb_obj_kind != STUBBORN) { | |
594 | GC_err_printf("debug_end_stubborn_change arg not stubborn: %p\n", p); | |
595 | ABORT("GC_debug_end_stubborn_change: arg not stubborn"); | |
596 | } | |
597 | GC_end_stubborn_change(q); | |
598 | } | |
599 | ||
600 | #else /* !STUBBORN_ALLOC */ | |
601 | ||
602 | void * GC_debug_malloc_stubborn(size_t lb, GC_EXTRA_PARAMS) | |
603 | { | |
604 | return GC_debug_malloc(lb, OPT_RA s, i); | |
605 | } | |
606 | ||
607 | void GC_debug_change_stubborn(void *p) | |
608 | { | |
609 | } | |
610 | ||
611 | void GC_debug_end_stubborn_change(void *p) | |
612 | { | |
613 | } | |
614 | ||
615 | #endif /* !STUBBORN_ALLOC */ | |
616 | ||
617 | void * GC_debug_malloc_atomic(size_t lb, GC_EXTRA_PARAMS) | |
618 | { | |
619 | void * result = GC_malloc_atomic(lb + DEBUG_BYTES); | |
620 | ||
621 | if (result == 0) { | |
622 | GC_err_printf("GC_debug_malloc_atomic(%lu) returning NIL (", | |
623 | (unsigned long) lb); | |
624 | GC_err_puts(s); | |
625 | GC_err_printf(":%lu)\n", (unsigned long)i); | |
626 | return(0); | |
627 | } | |
628 | if (!GC_debugging_started) { | |
629 | GC_start_debugging(); | |
630 | } | |
631 | ADD_CALL_CHAIN(result, ra); | |
632 | return (GC_store_debug_info(result, (word)lb, s, (word)i)); | |
633 | } | |
634 | ||
635 | char *GC_debug_strdup(const char *str, GC_EXTRA_PARAMS) | |
636 | { | |
637 | char *copy; | |
638 | if (str == NULL) return NULL; | |
639 | copy = GC_debug_malloc_atomic(strlen(str) + 1, OPT_RA s, i); | |
640 | if (copy == NULL) { | |
641 | errno = ENOMEM; | |
642 | return NULL; | |
643 | } | |
644 | strcpy(copy, str); | |
645 | return copy; | |
646 | } | |
647 | ||
648 | void * GC_debug_malloc_uncollectable(size_t lb, GC_EXTRA_PARAMS) | |
649 | { | |
650 | void * result = GC_malloc_uncollectable(lb + UNCOLLECTABLE_DEBUG_BYTES); | |
651 | ||
652 | if (result == 0) { | |
653 | GC_err_printf("GC_debug_malloc_uncollectable(%lu) returning NIL (", | |
654 | (unsigned long) lb); | |
655 | GC_err_puts(s); | |
656 | GC_err_printf(":%lu)\n", (unsigned long)i); | |
657 | return(0); | |
658 | } | |
659 | if (!GC_debugging_started) { | |
660 | GC_start_debugging(); | |
661 | } | |
662 | ADD_CALL_CHAIN(result, ra); | |
663 | return (GC_store_debug_info(result, (word)lb, s, (word)i)); | |
664 | } | |
665 | ||
666 | #ifdef ATOMIC_UNCOLLECTABLE | |
667 | void * GC_debug_malloc_atomic_uncollectable(size_t lb, GC_EXTRA_PARAMS) | |
668 | { | |
669 | void * result = | |
670 | GC_malloc_atomic_uncollectable(lb + UNCOLLECTABLE_DEBUG_BYTES); | |
671 | ||
672 | if (result == 0) { | |
673 | GC_err_printf( | |
674 | "GC_debug_malloc_atomic_uncollectable(%lu) returning NIL (", | |
675 | (unsigned long) lb); | |
676 | GC_err_puts(s); | |
677 | GC_err_printf(":%lu)\n", (unsigned long)i); | |
678 | return(0); | |
679 | } | |
680 | if (!GC_debugging_started) { | |
681 | GC_start_debugging(); | |
682 | } | |
683 | ADD_CALL_CHAIN(result, ra); | |
684 | return (GC_store_debug_info(result, (word)lb, s, (word)i)); | |
685 | } | |
686 | #endif /* ATOMIC_UNCOLLECTABLE */ | |
687 | ||
688 | void GC_debug_free(void * p) | |
689 | { | |
690 | ptr_t base; | |
691 | ptr_t clobbered; | |
692 | ||
693 | if (0 == p) return; | |
694 | base = GC_base(p); | |
695 | if (base == 0) { | |
696 | GC_err_printf("Attempt to free invalid pointer %p\n", p); | |
697 | ABORT("free(invalid pointer)"); | |
698 | } | |
699 | if ((ptr_t)p - (ptr_t)base != sizeof(oh)) { | |
700 | GC_err_printf( | |
701 | "GC_debug_free called on pointer %p wo debugging info\n", p); | |
702 | } else { | |
703 | # ifndef SHORT_DBG_HDRS | |
704 | clobbered = GC_check_annotated_obj((oh *)base); | |
705 | if (clobbered != 0) { | |
706 | if (((oh *)base) -> oh_sz == GC_size(base)) { | |
707 | GC_err_printf( | |
708 | "GC_debug_free: found previously deallocated (?) object at "); | |
709 | } else { | |
710 | GC_err_printf("GC_debug_free: found smashed location at "); | |
711 | } | |
712 | GC_print_smashed_obj(p, clobbered); | |
713 | } | |
714 | /* Invalidate size */ | |
715 | ((oh *)base) -> oh_sz = GC_size(base); | |
716 | # endif /* SHORT_DBG_HDRS */ | |
717 | } | |
718 | if (GC_find_leak) { | |
719 | GC_free(base); | |
720 | } else { | |
721 | hdr * hhdr = HDR(p); | |
722 | GC_bool uncollectable = FALSE; | |
723 | ||
724 | if (hhdr -> hb_obj_kind == UNCOLLECTABLE) { | |
725 | uncollectable = TRUE; | |
726 | } | |
727 | # ifdef ATOMIC_UNCOLLECTABLE | |
728 | if (hhdr -> hb_obj_kind == AUNCOLLECTABLE) { | |
729 | uncollectable = TRUE; | |
730 | } | |
731 | # endif | |
732 | if (uncollectable) { | |
733 | GC_free(base); | |
734 | } else { | |
735 | size_t i; | |
736 | size_t obj_sz = BYTES_TO_WORDS(hhdr -> hb_sz - sizeof(oh)); | |
737 | ||
738 | for (i = 0; i < obj_sz; ++i) ((word *)p)[i] = 0xdeadbeef; | |
739 | GC_ASSERT((word *)p + i == (word *)(base + hhdr -> hb_sz)); | |
740 | } | |
741 | } /* !GC_find_leak */ | |
742 | } | |
743 | ||
744 | #ifdef THREADS | |
745 | ||
746 | extern void GC_free_inner(void * p); | |
747 | ||
748 | /* Used internally; we assume it's called correctly. */ | |
749 | void GC_debug_free_inner(void * p) | |
750 | { | |
751 | GC_free_inner(GC_base(p)); | |
752 | } | |
753 | #endif | |
754 | ||
755 | void * GC_debug_realloc(void * p, size_t lb, GC_EXTRA_PARAMS) | |
756 | { | |
757 | void * base = GC_base(p); | |
758 | ptr_t clobbered; | |
759 | void * result; | |
760 | size_t copy_sz = lb; | |
761 | size_t old_sz; | |
762 | hdr * hhdr; | |
763 | ||
764 | if (p == 0) return(GC_debug_malloc(lb, OPT_RA s, i)); | |
765 | if (base == 0) { | |
766 | GC_err_printf("Attempt to reallocate invalid pointer %p\n", p); | |
767 | ABORT("realloc(invalid pointer)"); | |
768 | } | |
769 | if ((ptr_t)p - (ptr_t)base != sizeof(oh)) { | |
770 | GC_err_printf( | |
771 | "GC_debug_realloc called on pointer %p wo debugging info\n", p); | |
772 | return(GC_realloc(p, lb)); | |
773 | } | |
774 | hhdr = HDR(base); | |
775 | switch (hhdr -> hb_obj_kind) { | |
776 | # ifdef STUBBORN_ALLOC | |
777 | case STUBBORN: | |
778 | result = GC_debug_malloc_stubborn(lb, OPT_RA s, i); | |
779 | break; | |
780 | # endif | |
781 | case NORMAL: | |
782 | result = GC_debug_malloc(lb, OPT_RA s, i); | |
783 | break; | |
784 | case PTRFREE: | |
785 | result = GC_debug_malloc_atomic(lb, OPT_RA s, i); | |
786 | break; | |
787 | case UNCOLLECTABLE: | |
788 | result = GC_debug_malloc_uncollectable(lb, OPT_RA s, i); | |
789 | break; | |
790 | # ifdef ATOMIC_UNCOLLECTABLE | |
791 | case AUNCOLLECTABLE: | |
792 | result = GC_debug_malloc_atomic_uncollectable(lb, OPT_RA s, i); | |
793 | break; | |
794 | # endif | |
795 | default: | |
796 | GC_err_printf("GC_debug_realloc: encountered bad kind\n"); | |
797 | ABORT("bad kind"); | |
798 | } | |
799 | # ifdef SHORT_DBG_HDRS | |
800 | old_sz = GC_size(base) - sizeof(oh); | |
801 | # else | |
802 | clobbered = GC_check_annotated_obj((oh *)base); | |
803 | if (clobbered != 0) { | |
804 | GC_err_printf("GC_debug_realloc: found smashed location at "); | |
805 | GC_print_smashed_obj(p, clobbered); | |
806 | } | |
807 | old_sz = ((oh *)base) -> oh_sz; | |
808 | # endif | |
809 | if (old_sz < copy_sz) copy_sz = old_sz; | |
810 | if (result == 0) return(0); | |
811 | BCOPY(p, result, copy_sz); | |
812 | GC_debug_free(p); | |
813 | return(result); | |
814 | } | |
815 | ||
816 | #ifndef SHORT_DBG_HDRS | |
817 | ||
818 | /* List of smashed objects. We defer printing these, since we can't */ | |
819 | /* always print them nicely with the allocation lock held. */ | |
820 | /* We put them here instead of in GC_arrays, since it may be useful to */ | |
821 | /* be able to look at them with the debugger. */ | |
822 | #define MAX_SMASHED 20 | |
823 | ptr_t GC_smashed[MAX_SMASHED]; | |
824 | unsigned GC_n_smashed = 0; | |
825 | ||
826 | void GC_add_smashed(ptr_t smashed) | |
827 | { | |
828 | GC_ASSERT(GC_is_marked(GC_base(smashed))); | |
829 | GC_smashed[GC_n_smashed] = smashed; | |
830 | if (GC_n_smashed < MAX_SMASHED - 1) ++GC_n_smashed; | |
831 | /* In case of overflow, we keep the first MAX_SMASHED-1 */ | |
832 | /* entries plus the last one. */ | |
833 | GC_have_errors = TRUE; | |
834 | } | |
835 | ||
836 | /* Print all objects on the list. Clear the list. */ | |
837 | void GC_print_all_smashed_proc(void) | |
838 | { | |
839 | unsigned i; | |
840 | ||
841 | GC_ASSERT(I_DONT_HOLD_LOCK()); | |
842 | if (GC_n_smashed == 0) return; | |
843 | GC_err_printf("GC_check_heap_block: found smashed heap objects:\n"); | |
844 | for (i = 0; i < GC_n_smashed; ++i) { | |
845 | GC_print_smashed_obj(GC_base(GC_smashed[i]), GC_smashed[i]); | |
846 | GC_smashed[i] = 0; | |
847 | } | |
848 | GC_n_smashed = 0; | |
849 | } | |
850 | ||
851 | /* Check all marked objects in the given block for validity */ | |
852 | /* Avoid GC_apply_to_each_object for performance reasons. */ | |
853 | /*ARGSUSED*/ | |
854 | void GC_check_heap_block(struct hblk *hbp, word dummy) | |
855 | { | |
856 | struct hblkhdr * hhdr = HDR(hbp); | |
857 | size_t sz = hhdr -> hb_sz; | |
858 | size_t bit_no; | |
859 | char *p, *plim; | |
860 | ||
861 | p = hbp->hb_body; | |
862 | bit_no = 0; | |
863 | if (sz > MAXOBJBYTES) { | |
864 | plim = p; | |
865 | } else { | |
866 | plim = hbp->hb_body + HBLKSIZE - sz; | |
867 | } | |
868 | /* go through all words in block */ | |
869 | while( p <= plim ) { | |
870 | if( mark_bit_from_hdr(hhdr, bit_no) | |
871 | && GC_HAS_DEBUG_INFO((ptr_t)p)) { | |
872 | ptr_t clobbered = GC_check_annotated_obj((oh *)p); | |
873 | ||
874 | if (clobbered != 0) GC_add_smashed(clobbered); | |
875 | } | |
876 | bit_no += MARK_BIT_OFFSET(sz); | |
877 | p += sz; | |
878 | } | |
879 | } | |
880 | ||
881 | ||
882 | /* This assumes that all accessible objects are marked, and that */ | |
883 | /* I hold the allocation lock. Normally called by collector. */ | |
884 | void GC_check_heap_proc(void) | |
885 | { | |
886 | # ifndef SMALL_CONFIG | |
887 | /* Ignore gcc no effect warning on the following. */ | |
888 | GC_STATIC_ASSERT((sizeof(oh) & (GRANULE_BYTES - 1)) == 0); | |
889 | /* FIXME: Should we check for twice that alignment? */ | |
890 | # endif | |
891 | GC_apply_to_all_blocks(GC_check_heap_block, (word)0); | |
892 | } | |
893 | ||
894 | #endif /* !SHORT_DBG_HDRS */ | |
895 | ||
896 | struct closure { | |
897 | GC_finalization_proc cl_fn; | |
898 | void * cl_data; | |
899 | }; | |
900 | ||
901 | void * GC_make_closure(GC_finalization_proc fn, void * data) | |
902 | { | |
903 | struct closure * result = | |
904 | # ifdef DBG_HDRS_ALL | |
905 | (struct closure *) GC_debug_malloc(sizeof (struct closure), | |
906 | GC_EXTRAS); | |
907 | # else | |
908 | (struct closure *) GC_malloc(sizeof (struct closure)); | |
909 | # endif | |
910 | ||
911 | result -> cl_fn = fn; | |
912 | result -> cl_data = data; | |
913 | return((void *)result); | |
914 | } | |
915 | ||
916 | void GC_debug_invoke_finalizer(void * obj, void * data) | |
917 | { | |
918 | register struct closure * cl = (struct closure *) data; | |
919 | ||
920 | (*(cl -> cl_fn))((void *)((char *)obj + sizeof(oh)), cl -> cl_data); | |
921 | } | |
922 | ||
923 | /* Set ofn and ocd to reflect the values we got back. */ | |
924 | static void store_old (void *obj, GC_finalization_proc my_old_fn, | |
925 | struct closure *my_old_cd, GC_finalization_proc *ofn, | |
926 | void **ocd) | |
927 | { | |
928 | if (0 != my_old_fn) { | |
929 | if (my_old_fn != GC_debug_invoke_finalizer) { | |
930 | GC_err_printf("Debuggable object at %p had non-debug finalizer.\n", | |
931 | obj); | |
932 | /* This should probably be fatal. */ | |
933 | } else { | |
934 | if (ofn) *ofn = my_old_cd -> cl_fn; | |
935 | if (ocd) *ocd = my_old_cd -> cl_data; | |
936 | } | |
937 | } else { | |
938 | if (ofn) *ofn = 0; | |
939 | if (ocd) *ocd = 0; | |
940 | } | |
941 | } | |
942 | ||
943 | void GC_debug_register_finalizer(void * obj, GC_finalization_proc fn, | |
944 | void * cd, GC_finalization_proc *ofn, | |
945 | void * *ocd) | |
946 | { | |
947 | GC_finalization_proc my_old_fn; | |
948 | void * my_old_cd; | |
949 | ptr_t base = GC_base(obj); | |
950 | if (0 == base) return; | |
951 | if ((ptr_t)obj - base != sizeof(oh)) { | |
952 | GC_err_printf( | |
953 | "GC_debug_register_finalizer called with non-base-pointer %p\n", | |
954 | obj); | |
955 | } | |
956 | if (0 == fn) { | |
957 | GC_register_finalizer(base, 0, 0, &my_old_fn, &my_old_cd); | |
958 | } else { | |
959 | GC_register_finalizer(base, GC_debug_invoke_finalizer, | |
960 | GC_make_closure(fn,cd), &my_old_fn, &my_old_cd); | |
961 | } | |
962 | store_old(obj, my_old_fn, (struct closure *)my_old_cd, ofn, ocd); | |
963 | } | |
964 | ||
965 | void GC_debug_register_finalizer_no_order | |
966 | (void * obj, GC_finalization_proc fn, | |
967 | void * cd, GC_finalization_proc *ofn, | |
968 | void * *ocd) | |
969 | { | |
970 | GC_finalization_proc my_old_fn; | |
971 | void * my_old_cd; | |
972 | ptr_t base = GC_base(obj); | |
973 | if (0 == base) return; | |
974 | if ((ptr_t)obj - base != sizeof(oh)) { | |
975 | GC_err_printf( | |
976 | "GC_debug_register_finalizer_no_order called with " | |
977 | "non-base-pointer %p\n", | |
978 | obj); | |
979 | } | |
980 | if (0 == fn) { | |
981 | GC_register_finalizer_no_order(base, 0, 0, &my_old_fn, &my_old_cd); | |
982 | } else { | |
983 | GC_register_finalizer_no_order(base, GC_debug_invoke_finalizer, | |
984 | GC_make_closure(fn,cd), &my_old_fn, | |
985 | &my_old_cd); | |
986 | } | |
987 | store_old(obj, my_old_fn, (struct closure *)my_old_cd, ofn, ocd); | |
988 | } | |
989 | ||
990 | void GC_debug_register_finalizer_unreachable | |
991 | (void * obj, GC_finalization_proc fn, | |
992 | void * cd, GC_finalization_proc *ofn, | |
993 | void * *ocd) | |
994 | { | |
995 | GC_finalization_proc my_old_fn; | |
996 | void * my_old_cd; | |
997 | ptr_t base = GC_base(obj); | |
998 | if (0 == base) return; | |
999 | if ((ptr_t)obj - base != sizeof(oh)) { | |
1000 | GC_err_printf( | |
1001 | "GC_debug_register_finalizer_unreachable called with " | |
1002 | "non-base-pointer %p\n", | |
1003 | obj); | |
1004 | } | |
1005 | if (0 == fn) { | |
1006 | GC_register_finalizer_unreachable(base, 0, 0, &my_old_fn, &my_old_cd); | |
1007 | } else { | |
1008 | GC_register_finalizer_unreachable(base, GC_debug_invoke_finalizer, | |
1009 | GC_make_closure(fn,cd), &my_old_fn, | |
1010 | &my_old_cd); | |
1011 | } | |
1012 | store_old(obj, my_old_fn, (struct closure *)my_old_cd, ofn, ocd); | |
1013 | } | |
1014 | ||
1015 | void GC_debug_register_finalizer_ignore_self | |
1016 | (void * obj, GC_finalization_proc fn, | |
1017 | void * cd, GC_finalization_proc *ofn, | |
1018 | void * *ocd) | |
1019 | { | |
1020 | GC_finalization_proc my_old_fn; | |
1021 | void * my_old_cd; | |
1022 | ptr_t base = GC_base(obj); | |
1023 | if (0 == base) return; | |
1024 | if ((ptr_t)obj - base != sizeof(oh)) { | |
1025 | GC_err_printf( | |
1026 | "GC_debug_register_finalizer_ignore_self called with " | |
1027 | "non-base-pointer %p\n", obj); | |
1028 | } | |
1029 | if (0 == fn) { | |
1030 | GC_register_finalizer_ignore_self(base, 0, 0, &my_old_fn, &my_old_cd); | |
1031 | } else { | |
1032 | GC_register_finalizer_ignore_self(base, GC_debug_invoke_finalizer, | |
1033 | GC_make_closure(fn,cd), &my_old_fn, | |
1034 | &my_old_cd); | |
1035 | } | |
1036 | store_old(obj, my_old_fn, (struct closure *)my_old_cd, ofn, ocd); | |
1037 | } | |
1038 | ||
1039 | #ifdef GC_ADD_CALLER | |
1040 | # define RA GC_RETURN_ADDR, | |
1041 | #else | |
1042 | # define RA | |
1043 | #endif | |
1044 | ||
1045 | void * GC_debug_malloc_replacement(size_t lb) | |
1046 | { | |
1047 | return GC_debug_malloc(lb, RA "unknown", 0); | |
1048 | } | |
1049 | ||
1050 | void * GC_debug_realloc_replacement(void *p, size_t lb) | |
1051 | { | |
1052 | return GC_debug_realloc(p, lb, RA "unknown", 0); | |
1053 | } |