Merge pull request #600 from tr8dr/master
[mono.git] / mono / metadata / sgen-debug.c
1 /*
2  * sgen-debug.c: Collector debugging
3  *
4  * Author:
5  *      Paolo Molaro (lupus@ximian.com)
6  *  Rodrigo Kumpera (kumpera@gmail.com)
7  *
8  * Copyright 2005-2011 Novell, Inc (http://www.novell.com)
9  * Copyright 2011 Xamarin Inc (http://www.xamarin.com)
10  * Copyright 2011 Xamarin, Inc.
11  * Copyright (C) 2012 Xamarin Inc
12  *
13  * This library is free software; you can redistribute it and/or
14  * modify it under the terms of the GNU Library General Public
15  * License 2.0 as published by the Free Software Foundation;
16  *
17  * This library is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20  * Library General Public License for more details.
21  *
22  * You should have received a copy of the GNU Library General Public
23  * License 2.0 along with this library; if not, write to the Free
24  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25  */
26
27 #include "config.h"
28 #ifdef HAVE_SGEN_GC
29
30 #include "metadata/sgen-gc.h"
31 #include "metadata/sgen-cardtable.h"
32 #include "metadata/sgen-protocol.h"
33 #include "metadata/sgen-memory-governor.h"
34 #include "metadata/sgen-pinning.h"
35
36 #define LOAD_VTABLE     SGEN_LOAD_VTABLE
37
38 #define object_is_forwarded     SGEN_OBJECT_IS_FORWARDED
39 #define object_is_pinned        SGEN_OBJECT_IS_PINNED
40 #define safe_object_get_size    sgen_safe_object_get_size
41
42 void describe_ptr (char *ptr);
43 void check_object (char *start);
44
45 /*
46  * ######################################################################
47  * ########  Collector debugging
48  * ######################################################################
49  */
50
51 const char*descriptor_types [] = {
52         "INVALID",
53         "run_length",
54         "small_bitmap",
55         "complex",
56         "vector",
57         "large_bitmap",
58         "complex_arr",
59         "complex_ptrfree"
60 };
61
62 static char* describe_nursery_ptr (char *ptr, gboolean need_setup);
63
64 static void
65 describe_pointer (char *ptr, gboolean need_setup)
66 {
67         MonoVTable *vtable;
68         mword desc;
69         int type;
70         char *start;
71         char *forwarded;
72         mword size;
73
74  restart:
75         if (sgen_ptr_in_nursery (ptr)) {
76                 start = describe_nursery_ptr (ptr, need_setup);
77                 if (!start)
78                         return;
79                 ptr = start;
80         } else {
81                 if (sgen_ptr_is_in_los (ptr, &start)) {
82                         if (ptr == start)
83                                 printf ("Pointer is the start of object %p in LOS space.\n", start);
84                         else
85                                 printf ("Pointer is at offset 0x%x of object %p in LOS space.\n", (int)(ptr - start), start);
86                         ptr = start;
87                         mono_sgen_los_describe_pointer (ptr);
88                 } else if (major_collector.ptr_is_in_non_pinned_space (ptr, &start)) {
89                         if (ptr == start)
90                                 printf ("Pointer is the start of object %p in oldspace.\n", start);
91                         else if (start)
92                                 printf ("Pointer is at offset 0x%x of object %p in oldspace.\n", (int)(ptr - start), start);
93                         else
94                                 printf ("Pointer inside oldspace.\n");
95                         if (start)
96                                 ptr = start;
97                         major_collector.describe_pointer (ptr);
98                 } else if (major_collector.obj_is_from_pinned_alloc (ptr)) {
99                         printf ("Pointer is inside a pinned chunk.\n");
100                 } else {
101                         printf ("Pointer unknown.\n");
102                         return;
103                 }
104         }
105
106         if (object_is_pinned (ptr))
107                 printf ("Object is pinned.\n");
108
109         if ((forwarded = object_is_forwarded (ptr))) {
110                 printf ("Object is forwarded to %p:\n", forwarded);
111                 ptr = forwarded;
112                 goto restart;
113         }
114
115         // FIXME: Handle pointers to the inside of objects
116         vtable = (MonoVTable*)LOAD_VTABLE (ptr);
117
118         printf ("VTable: %p\n", vtable);
119         if (vtable == NULL) {
120                 printf ("VTable is invalid (empty).\n");
121                 return;
122         }
123         if (sgen_ptr_in_nursery (vtable)) {
124                 printf ("VTable is invalid (points inside nursery).\n");
125                 return;
126         }
127         printf ("Class: %s\n", vtable->klass->name);
128
129         desc = ((GCVTable*)vtable)->desc;
130         printf ("Descriptor: %lx\n", (long)desc);
131
132         type = desc & 0x7;
133         printf ("Descriptor type: %d (%s)\n", type, descriptor_types [type]);
134
135         size = sgen_safe_object_get_size ((MonoObject*)ptr);
136         printf ("Size: %td\n", size);
137 }
138
139 void
140 describe_ptr (char *ptr)
141 {
142         describe_pointer (ptr, TRUE);
143 }
144
145 static gboolean missing_remsets;
146
147 /*
148  * We let a missing remset slide if the target object is pinned,
149  * because the store might have happened but the remset not yet added,
150  * but in that case the target must be pinned.  We might theoretically
151  * miss some missing remsets this way, but it's very unlikely.
152  */
153 #undef HANDLE_PTR
154 #define HANDLE_PTR(ptr,obj)     do {    \
155         if (*(ptr) && sgen_ptr_in_nursery ((char*)*(ptr))) { \
156                 if (!sgen_get_remset ()->find_address ((char*)(ptr))) { \
157                         SGEN_LOG (0, "Oldspace->newspace reference %p at offset %td in object %p (%s.%s) not found in remsets.", *(ptr), (char*)(ptr) - (char*)(obj), (obj), ((MonoObject*)(obj))->vtable->klass->name_space, ((MonoObject*)(obj))->vtable->klass->name); \
158                         binary_protocol_missing_remset ((obj), (gpointer)LOAD_VTABLE ((obj)), (char*)(ptr) - (char*)(obj), *(ptr), (gpointer)LOAD_VTABLE(*(ptr)), object_is_pinned (*(ptr))); \
159                         if (!object_is_pinned (*(ptr)))                                                         \
160                                 missing_remsets = TRUE;                                                                 \
161                 }                                                                                                                               \
162         }                                                                                                                                       \
163         } while (0)
164
165 /*
166  * Check that each object reference which points into the nursery can
167  * be found in the remembered sets.
168  */
169 static void
170 check_consistency_callback (char *start, size_t size, void *dummy)
171 {
172         GCVTable *vt = (GCVTable*)LOAD_VTABLE (start);
173         SGEN_LOG (8, "Scanning object %p, vtable: %p (%s)", start, vt, vt->klass->name);
174
175 #include "sgen-scan-object.h"
176 }
177
178 /*
179  * Perform consistency check of the heap.
180  *
181  * Assumes the world is stopped.
182  */
183 void
184 sgen_check_consistency (void)
185 {
186         // Need to add more checks
187
188         missing_remsets = FALSE;
189
190         SGEN_LOG (1, "Begin heap consistency check...");
191
192         // Check that oldspace->newspace pointers are registered with the collector
193         major_collector.iterate_objects (TRUE, TRUE, (IterateObjectCallbackFunc)check_consistency_callback, NULL);
194
195         sgen_los_iterate_objects ((IterateObjectCallbackFunc)check_consistency_callback, NULL);
196
197         SGEN_LOG (1, "Heap consistency check done.");
198
199         if (!binary_protocol_is_enabled ())
200                 g_assert (!missing_remsets);
201 }
202
203 static gboolean
204 is_major_or_los_object_marked (char *obj)
205 {
206         if (sgen_safe_object_get_size ((MonoObject*)obj) > SGEN_MAX_SMALL_OBJ_SIZE) {
207                 return sgen_los_object_is_pinned (obj);
208         } else {
209                 return sgen_get_major_collector ()->is_object_live (obj);
210         }
211 }
212
213 #undef HANDLE_PTR
214 #define HANDLE_PTR(ptr,obj)     do {    \
215         if (*(ptr) && !sgen_ptr_in_nursery ((char*)*(ptr)) && !is_major_or_los_object_marked ((char*)*(ptr))) { \
216                 if (!sgen_get_remset ()->find_address_with_cards (start, cards, (char*)(ptr))) { \
217                         SGEN_LOG (0, "major->major reference %p at offset %td in object %p (%s.%s) not found in remsets.", *(ptr), (char*)(ptr) - (char*)(obj), (obj), ((MonoObject*)(obj))->vtable->klass->name_space, ((MonoObject*)(obj))->vtable->klass->name); \
218                         binary_protocol_missing_remset ((obj), (gpointer)LOAD_VTABLE ((obj)), (char*)(ptr) - (char*)(obj), *(ptr), (gpointer)LOAD_VTABLE(*(ptr)), object_is_pinned (*(ptr))); \
219                 }                                                                                                                               \
220         }                                                                                                                                       \
221         } while (0)
222
223 static void
224 check_mod_union_callback (char *start, size_t size, void *dummy)
225 {
226         gboolean in_los = (gboolean) (size_t) dummy;
227         GCVTable *vt = (GCVTable*)LOAD_VTABLE (start);
228         guint8 *cards;
229         SGEN_LOG (8, "Scanning object %p, vtable: %p (%s)", start, vt, vt->klass->name);
230
231         if (!is_major_or_los_object_marked (start))
232                 return;
233
234         if (in_los)
235                 cards = sgen_los_header_for_object (start)->cardtable_mod_union;
236         else
237                 cards = sgen_get_major_collector ()->get_cardtable_mod_union_for_object (start);
238
239         SGEN_ASSERT (0, cards, "we must have mod union for marked major objects");
240
241 #include "sgen-scan-object.h"
242 }
243
244 void
245 sgen_check_mod_union_consistency (void)
246 {
247         missing_remsets = FALSE;
248
249         major_collector.iterate_objects (TRUE, TRUE, (IterateObjectCallbackFunc)check_mod_union_callback, (void*)FALSE);
250
251         sgen_los_iterate_objects ((IterateObjectCallbackFunc)check_mod_union_callback, (void*)TRUE);
252
253         if (!binary_protocol_is_enabled ())
254                 g_assert (!missing_remsets);
255 }
256
257 #undef HANDLE_PTR
258 #define HANDLE_PTR(ptr,obj)     do {                                    \
259                 if (*(ptr) && !LOAD_VTABLE (*(ptr)))                                            \
260                         g_error ("Could not load vtable for obj %p slot %d (size %d)", obj, (char*)ptr - (char*)obj, safe_object_get_size ((MonoObject*)obj));          \
261         } while (0)
262
263 static void
264 check_major_refs_callback (char *start, size_t size, void *dummy)
265 {
266 #include "sgen-scan-object.h"
267 }
268
269 void
270 sgen_check_major_refs (void)
271 {
272         major_collector.iterate_objects (TRUE, TRUE, (IterateObjectCallbackFunc)check_major_refs_callback, NULL);
273         sgen_los_iterate_objects ((IterateObjectCallbackFunc)check_major_refs_callback, NULL);
274 }
275
276 /* Check that the reference is valid */
277 #undef HANDLE_PTR
278 #define HANDLE_PTR(ptr,obj)     do {    \
279                 if (*(ptr)) {   \
280                         g_assert (sgen_safe_name (*(ptr)) != NULL);     \
281                 }       \
282         } while (0)
283
284 /*
285  * check_object:
286  *
287  *   Perform consistency check on an object. Currently we only check that the
288  * reference fields are valid.
289  */
290 void
291 check_object (char *start)
292 {
293         if (!start)
294                 return;
295
296 #include "sgen-scan-object.h"
297 }
298
299
300 static char **valid_nursery_objects;
301 static int valid_nursery_object_count;
302 static gboolean broken_heap;
303
304 static void 
305 setup_mono_sgen_scan_area_with_callback (char *object, size_t size, void *data)
306 {
307         valid_nursery_objects [valid_nursery_object_count++] = object;
308 }
309
310 static void
311 setup_valid_nursery_objects (void)
312 {
313         if (!valid_nursery_objects)
314                 valid_nursery_objects = sgen_alloc_os_memory (DEFAULT_NURSERY_SIZE, SGEN_ALLOC_INTERNAL | SGEN_ALLOC_ACTIVATE, "debugging data");
315         valid_nursery_object_count = 0;
316         sgen_scan_area_with_callback (nursery_section->data, nursery_section->end_data, setup_mono_sgen_scan_area_with_callback, NULL, FALSE);
317 }
318
319 static gboolean
320 find_object_in_nursery_dump (char *object)
321 {
322         int first = 0, last = valid_nursery_object_count;
323         while (first < last) {
324                 int middle = first + ((last - first) >> 1);
325                 if (object == valid_nursery_objects [middle])
326                         return TRUE;
327
328                 if (object < valid_nursery_objects [middle])
329                         last = middle;
330                 else
331                         first = middle + 1;
332         }
333         g_assert (first == last);
334         return FALSE;
335 }
336
337 static char*
338 describe_nursery_ptr (char *ptr, gboolean need_setup)
339 {
340         int i;
341
342         if (need_setup)
343                 setup_valid_nursery_objects ();
344
345         for (i = 0; i < valid_nursery_object_count; ++i) {
346                 if (valid_nursery_objects [i] >= ptr)
347                         break;
348         }
349
350         if (i >= valid_nursery_object_count || valid_nursery_objects [i] + safe_object_get_size ((MonoObject *)valid_nursery_objects [i]) < ptr) {
351                 SGEN_LOG (0, "nursery-ptr (unalloc'd-memory)\n");
352                 return NULL;
353         } else {
354                 char *obj = valid_nursery_objects [i];
355                 if (obj == ptr)
356                         SGEN_LOG (0, "nursery-ptr\n");
357                 else
358                         SGEN_LOG (0, "nursery-ptr (interior-ptr offset %td)\n", ptr - obj);
359                 return obj;
360         }
361 }
362
363 static gboolean
364 is_valid_object_pointer (char *object)
365 {
366         if (sgen_ptr_in_nursery (object))
367                 return find_object_in_nursery_dump (object);
368         
369         if (sgen_los_is_valid_object (object))
370                 return TRUE;
371
372         if (major_collector.is_valid_object (object))
373                 return TRUE;
374         return FALSE;
375 }
376
377 static void
378 bad_pointer_spew (char *obj, char **slot)
379 {
380         char *ptr = *slot;
381         MonoVTable *vtable = (MonoVTable*)LOAD_VTABLE (obj);
382
383         SGEN_LOG (0, "Invalid object pointer %p at offset %td in object %p (%s.%s):", ptr,
384                 (char*)slot - obj,
385                 obj, vtable->klass->name_space, vtable->klass->name);
386         describe_pointer (ptr, FALSE);
387         broken_heap = TRUE;
388 }
389
390 static void
391 missing_remset_spew (char *obj, char **slot)
392 {
393         char *ptr = *slot;
394         MonoVTable *vtable = (MonoVTable*)LOAD_VTABLE (obj);
395
396         SGEN_LOG (0, "Oldspace->newspace reference %p at offset %td in object %p (%s.%s) not found in remsets.",
397                 ptr, (char*)slot - obj, obj, 
398                 vtable->klass->name_space, vtable->klass->name);
399
400         broken_heap = TRUE;
401 }
402
403 /*
404 FIXME Flag missing remsets due to pinning as non fatal
405 */
406 #undef HANDLE_PTR
407 #define HANDLE_PTR(ptr,obj)     do {    \
408                 if (*(char**)ptr) {     \
409                         if (!is_valid_object_pointer (*(char**)ptr)) {  \
410                                 bad_pointer_spew ((char*)obj, (char**)ptr);     \
411                         } else if (!sgen_ptr_in_nursery (obj) && sgen_ptr_in_nursery ((char*)*ptr)) {   \
412                                 if (!sgen_get_remset ()->find_address ((char*)(ptr)) && !sgen_cement_lookup ((char*)*(ptr)) && (!allow_missing_pinned || !SGEN_OBJECT_IS_PINNED ((char*)*(ptr)))) \
413                                 missing_remset_spew ((char*)obj, (char**)ptr);  \
414                         }       \
415         } \
416         } while (0)
417
418 static void
419 verify_object_pointers_callback (char *start, size_t size, void *data)
420 {
421         gboolean allow_missing_pinned = (gboolean) (size_t) data;
422
423 #include "sgen-scan-object.h"
424 }
425
426 /*
427 FIXME:
428 -This heap checker is racy regarding inlined write barriers and other JIT tricks that
429 depend on OP_DUMMY_USE.
430 */
431 void
432 sgen_check_whole_heap (gboolean allow_missing_pinned)
433 {
434         setup_valid_nursery_objects ();
435
436         broken_heap = FALSE;
437         sgen_scan_area_with_callback (nursery_section->data, nursery_section->end_data, verify_object_pointers_callback, (void*) (size_t) allow_missing_pinned, FALSE);
438         major_collector.iterate_objects (TRUE, TRUE, verify_object_pointers_callback, (void*) (size_t) allow_missing_pinned);
439         sgen_los_iterate_objects (verify_object_pointers_callback, (void*) (size_t) allow_missing_pinned);
440
441         g_assert (!broken_heap);
442 }
443
444 static gboolean
445 ptr_in_heap (char *object)
446 {
447         if (sgen_ptr_in_nursery (object))
448                 return TRUE;
449         
450         if (sgen_los_is_valid_object (object))
451                 return TRUE;
452
453         if (major_collector.is_valid_object (object))
454                 return TRUE;
455         return FALSE;
456 }
457
458 /*
459  * sgen_check_objref:
460  *   Do consistency checks on the object reference OBJ. Assert on failure.
461  */
462 void
463 sgen_check_objref (char *obj)
464 {
465         g_assert (ptr_in_heap (obj));
466 }
467
468 static void
469 find_pinning_ref_from_thread (char *obj, size_t size)
470 {
471         int j;
472         SgenThreadInfo *info;
473         char *endobj = obj + size;
474
475         FOREACH_THREAD (info) {
476                 char **start = (char**)info->stack_start;
477                 if (info->skip)
478                         continue;
479                 while (start < (char**)info->stack_end) {
480                         if (*start >= obj && *start < endobj) {
481                                 SGEN_LOG (0, "Object %p referenced in thread %p (id %p) at %p, stack: %p-%p", obj, info, (gpointer)mono_thread_info_get_tid (info), start, info->stack_start, info->stack_end);
482                         }
483                         start++;
484                 }
485
486                 for (j = 0; j < ARCH_NUM_REGS; ++j) {
487 #ifdef USE_MONO_CTX
488                         mword w = ((mword*)&info->ctx) [j];
489 #else
490                         mword w = (mword)&info->regs [j];
491 #endif
492
493                         if (w >= (mword)obj && w < (mword)obj + size)
494                                 SGEN_LOG (0, "Object %p referenced in saved reg %d of thread %p (id %p)", obj, j, info, (gpointer)mono_thread_info_get_tid (info));
495                 } END_FOREACH_THREAD
496         }
497 }
498
499 /*
500  * Debugging function: find in the conservative roots where @obj is being pinned.
501  */
502 static G_GNUC_UNUSED void
503 find_pinning_reference (char *obj, size_t size)
504 {
505         char **start;
506         RootRecord *root;
507         char *endobj = obj + size;
508
509         SGEN_HASH_TABLE_FOREACH (&roots_hash [ROOT_TYPE_NORMAL], start, root) {
510                 /* if desc is non-null it has precise info */
511                 if (!root->root_desc) {
512                         while (start < (char**)root->end_root) {
513                                 if (*start >= obj && *start < endobj) {
514                                         SGEN_LOG (0, "Object %p referenced in pinned roots %p-%p\n", obj, start, root->end_root);
515                                 }
516                                 start++;
517                         }
518                 }
519         } SGEN_HASH_TABLE_FOREACH_END;
520
521         find_pinning_ref_from_thread (obj, size);
522 }
523
524 #undef HANDLE_PTR
525 #define HANDLE_PTR(ptr,obj)     do {                                    \
526                 char* __target = *(char**)ptr;                          \
527                 if (__target) {                                         \
528                         g_assert (is_valid_object_pointer (__target));  \
529                         if (sgen_ptr_in_nursery (__target)) {           \
530                                 g_assert (SGEN_OBJECT_IS_PINNED (__target)); \
531                         } else if (sgen_los_is_valid_object (__target)) { \
532                                 g_assert (sgen_los_object_is_pinned (__target)); \
533                         } else if (major_collector.is_valid_object (__target)) { \
534                                 g_assert (major_collector.is_object_live (__target)); \
535                         } else {                                        \
536                                 g_assert_not_reached ();                \
537                         }                                               \
538                 }                                                       \
539         } while (0)
540
541 static void
542 check_marked_callback (char *start, size_t size, void *dummy)
543 {
544         gboolean is_los = (gboolean) (size_t) dummy;
545
546         if (is_los) {
547                 if (!sgen_los_object_is_pinned (start))
548                         return;
549         } else {
550                 if (!major_collector.is_object_live (start))
551                         return;
552         }
553
554 #include "sgen-scan-object.h"
555 }
556
557 void
558 sgen_check_major_heap_marked (void)
559 {
560         setup_valid_nursery_objects ();
561
562         major_collector.iterate_objects (TRUE, TRUE, check_marked_callback, (void*)FALSE);
563         sgen_los_iterate_objects (check_marked_callback, (void*)TRUE);
564 }
565
566 static void
567 check_nursery_objects_pinned_callback (char *obj, size_t size, void *data /* ScanCopyContext *ctx */)
568 {
569         gboolean pinned = (gboolean) (size_t) data;
570
571         g_assert (!SGEN_OBJECT_IS_FORWARDED (obj));
572         if (pinned)
573                 g_assert (SGEN_OBJECT_IS_PINNED (obj));
574         else
575                 g_assert (!SGEN_OBJECT_IS_PINNED (obj));
576 }
577
578 void
579 sgen_check_nursery_objects_pinned (gboolean pinned)
580 {
581         sgen_clear_nursery_fragments ();
582         sgen_scan_area_with_callback (nursery_section->data, nursery_section->end_data,
583                         (IterateObjectCallbackFunc)check_nursery_objects_pinned_callback, (void*) (size_t) pinned /* (void*)&ctx */, FALSE);
584 }
585
586 #endif /*HAVE_SGEN_GC*/