/**
|
* \file
|
* Collector debugging
|
*
|
* Author:
|
* Paolo Molaro (lupus@ximian.com)
|
* Rodrigo Kumpera (kumpera@gmail.com)
|
*
|
* Copyright 2005-2011 Novell, Inc (http://www.novell.com)
|
* Copyright 2011 Xamarin Inc (http://www.xamarin.com)
|
* Copyright 2011 Xamarin, Inc.
|
* Copyright (C) 2012 Xamarin Inc
|
*
|
* Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
*/
|
|
#include "config.h"
|
#ifdef HAVE_SGEN_GC
|
|
#include <string.h>
|
|
#include "mono/sgen/sgen-gc.h"
|
#include "mono/sgen/sgen-cardtable.h"
|
#include "mono/sgen/sgen-protocol.h"
|
#include "mono/sgen/sgen-memory-governor.h"
|
#include "mono/sgen/sgen-pinning.h"
|
#include "mono/sgen/sgen-client.h"
|
|
#define LOAD_VTABLE SGEN_LOAD_VTABLE
|
|
#define object_is_forwarded SGEN_OBJECT_IS_FORWARDED
|
#define object_is_pinned SGEN_OBJECT_IS_PINNED
|
#define safe_object_get_size sgen_safe_object_get_size
|
|
void describe_ptr (char *ptr);
|
void check_object (GCObject *obj);
|
|
/*
|
* ######################################################################
|
* ######## Collector debugging
|
* ######################################################################
|
*/
|
|
static const char*descriptor_types [] = {
|
"INVALID",
|
"run length",
|
"bitmap",
|
"small pointer-free",
|
"complex",
|
"vector",
|
"complex arrray",
|
"complex pointer-free"
|
};
|
|
static char* describe_nursery_ptr (char *ptr, gboolean need_setup);
|
|
static void
|
describe_pointer (char *ptr, gboolean need_setup)
|
{
|
GCVTable vtable;
|
SgenDescriptor desc;
|
int type;
|
char *start;
|
char *forwarded;
|
mword size;
|
|
restart:
|
if (sgen_ptr_in_nursery (ptr)) {
|
start = describe_nursery_ptr (ptr, need_setup);
|
if (!start)
|
return;
|
ptr = start;
|
vtable = LOAD_VTABLE ((GCObject*)ptr);
|
} else {
|
if (sgen_ptr_is_in_los (ptr, &start)) {
|
if (ptr == start)
|
printf ("Pointer is the start of object %p in LOS space.\n", start);
|
else
|
printf ("Pointer is at offset 0x%x of object %p in LOS space.\n", (int)(ptr - start), start);
|
ptr = start;
|
mono_sgen_los_describe_pointer (ptr);
|
vtable = LOAD_VTABLE ((GCObject*)ptr);
|
} else if (major_collector.ptr_is_in_non_pinned_space (ptr, &start)) {
|
if (ptr == start)
|
printf ("Pointer is the start of object %p in oldspace.\n", start);
|
else if (start)
|
printf ("Pointer is at offset 0x%x of object %p in oldspace.\n", (int)(ptr - start), start);
|
else
|
printf ("Pointer inside oldspace.\n");
|
if (start)
|
ptr = start;
|
vtable = (GCVTable)major_collector.describe_pointer (ptr);
|
} else if (major_collector.ptr_is_from_pinned_alloc (ptr)) {
|
// FIXME: Handle pointers to the inside of objects
|
printf ("Pointer is inside a pinned chunk.\n");
|
vtable = LOAD_VTABLE ((GCObject*)ptr);
|
} else {
|
printf ("Pointer unknown.\n");
|
return;
|
}
|
}
|
|
if (object_is_pinned (ptr))
|
printf ("Object is pinned.\n");
|
|
if ((forwarded = (char *)object_is_forwarded (ptr))) {
|
printf ("Object is forwarded to %p:\n", forwarded);
|
ptr = forwarded;
|
goto restart;
|
}
|
|
printf ("VTable: %p\n", vtable);
|
if (vtable == NULL) {
|
printf ("VTable is invalid (empty).\n");
|
goto invalid_vtable;
|
}
|
if (sgen_ptr_in_nursery (vtable)) {
|
printf ("VTable is invalid (points inside nursery).\n");
|
goto invalid_vtable;
|
}
|
printf ("Class: %s.%s\n", sgen_client_vtable_get_namespace (vtable), sgen_client_vtable_get_name (vtable));
|
|
desc = sgen_vtable_get_descriptor (vtable);
|
printf ("Descriptor: %lx\n", (long)desc);
|
|
type = desc & DESC_TYPE_MASK;
|
printf ("Descriptor type: %d (%s)\n", type, descriptor_types [type]);
|
|
size = sgen_safe_object_get_size ((GCObject*)ptr);
|
printf ("Size: %d\n", (int)size);
|
|
invalid_vtable:
|
;
|
sgen_client_describe_invalid_pointer ((GCObject *) ptr);
|
}
|
|
void
|
describe_ptr (char *ptr)
|
{
|
describe_pointer (ptr, TRUE);
|
}
|
|
static gboolean missing_remsets;
|
|
/*
|
* We let a missing remset slide if the target object is pinned,
|
* because the store might have happened but the remset not yet added,
|
* but in that case the target must be pinned. We might theoretically
|
* miss some missing remsets this way, but it's very unlikely.
|
*/
|
#undef HANDLE_PTR
|
#define HANDLE_PTR(ptr,obj) do { \
|
if (*(ptr) && sgen_ptr_in_nursery ((char*)*(ptr))) { \
|
if (!sgen_get_remset ()->find_address ((char*)(ptr)) && !sgen_cement_lookup (*(ptr))) { \
|
GCVTable __vt = SGEN_LOAD_VTABLE (obj); \
|
gboolean is_pinned = object_is_pinned (*(ptr)); \
|
SGEN_LOG (0, "Oldspace->newspace reference %p at offset %zd in object %p (%s.%s) not found in remsets%s.", *(ptr), (char*)(ptr) - (char*)(obj), (obj), sgen_client_vtable_get_namespace (__vt), sgen_client_vtable_get_name (__vt), is_pinned ? ", but object is pinned" : ""); \
|
binary_protocol_missing_remset ((obj), __vt, (int) ((char*)(ptr) - (char*)(obj)), *(ptr), (gpointer)LOAD_VTABLE(*(ptr)), is_pinned); \
|
if (!is_pinned) \
|
missing_remsets = TRUE; \
|
} \
|
} \
|
} while (0)
|
|
/*
|
* Check that each object reference which points into the nursery can
|
* be found in the remembered sets.
|
*/
|
static void
|
check_consistency_callback (GCObject *obj, size_t size, void *dummy)
|
{
|
char *start = (char*)obj;
|
GCVTable vt = LOAD_VTABLE (obj);
|
SgenDescriptor desc = sgen_vtable_get_descriptor (vt);
|
SGEN_LOG (8, "Scanning object %p, vtable: %p (%s)", start, vt, sgen_client_vtable_get_name (vt));
|
|
#include "sgen-scan-object.h"
|
}
|
|
/*
|
* Perform consistency check of the heap.
|
*
|
* Assumes the world is stopped.
|
*/
|
void
|
sgen_check_remset_consistency (void)
|
{
|
// Need to add more checks
|
|
missing_remsets = FALSE;
|
|
SGEN_LOG (1, "Begin heap consistency check...");
|
|
// Check that oldspace->newspace pointers are registered with the collector
|
major_collector.iterate_objects (ITERATE_OBJECTS_SWEEP_ALL, (IterateObjectCallbackFunc)check_consistency_callback, NULL);
|
|
sgen_los_iterate_objects ((IterateObjectCallbackFunc)check_consistency_callback, NULL);
|
|
SGEN_LOG (1, "Heap consistency check done.");
|
|
if (missing_remsets)
|
binary_protocol_flush_buffers (TRUE);
|
if (!binary_protocol_is_enabled ())
|
g_assert (!missing_remsets);
|
}
|
|
static gboolean
|
is_major_or_los_object_marked (GCObject *obj)
|
{
|
if (sgen_safe_object_get_size ((GCObject*)obj) > SGEN_MAX_SMALL_OBJ_SIZE) {
|
return sgen_los_object_is_pinned (obj);
|
} else {
|
return sgen_get_major_collector ()->is_object_live (obj);
|
}
|
}
|
|
#undef HANDLE_PTR
|
#define HANDLE_PTR(ptr,obj) do { \
|
if (*(ptr) && !sgen_ptr_in_nursery ((char*)*(ptr)) && !is_major_or_los_object_marked ((GCObject*)*(ptr))) { \
|
if (!cards || !sgen_get_remset ()->find_address_with_cards (start, cards, (char*)(ptr))) { \
|
GCVTable __vt = SGEN_LOAD_VTABLE (obj); \
|
SGEN_LOG (0, "major->major reference %p at offset %zd in object %p (%s.%s) not found in remsets.", *(ptr), (char*)(ptr) - (char*)(obj), (obj), sgen_client_vtable_get_namespace (__vt), sgen_client_vtable_get_name (__vt)); \
|
binary_protocol_missing_remset ((obj), __vt, (int) ((char*)(ptr) - (char*)(obj)), *(ptr), (gpointer)LOAD_VTABLE(*(ptr)), object_is_pinned (*(ptr))); \
|
missing_remsets = TRUE; \
|
} \
|
} \
|
} while (0)
|
|
static void
|
check_mod_union_callback (GCObject *obj, size_t size, void *dummy)
|
{
|
char *start = (char*)obj;
|
gboolean in_los = (gboolean) (size_t) dummy;
|
GCVTable vt = LOAD_VTABLE (obj);
|
SgenDescriptor desc = sgen_vtable_get_descriptor (vt);
|
guint8 *cards;
|
SGEN_LOG (8, "Scanning object %p, vtable: %p (%s)", obj, vt, sgen_client_vtable_get_name (vt));
|
|
if (!is_major_or_los_object_marked (obj))
|
return;
|
|
if (in_los)
|
cards = sgen_los_header_for_object (obj)->cardtable_mod_union;
|
else
|
cards = sgen_get_major_collector ()->get_cardtable_mod_union_for_reference (start);
|
|
#include "sgen-scan-object.h"
|
}
|
|
void
|
sgen_check_mod_union_consistency (void)
|
{
|
missing_remsets = FALSE;
|
|
major_collector.iterate_objects (ITERATE_OBJECTS_SWEEP_ALL, (IterateObjectCallbackFunc)check_mod_union_callback, (void*)FALSE);
|
|
sgen_los_iterate_objects ((IterateObjectCallbackFunc)check_mod_union_callback, (void*)TRUE);
|
|
if (!binary_protocol_is_enabled ())
|
g_assert (!missing_remsets);
|
}
|
|
#undef HANDLE_PTR
|
#define HANDLE_PTR(ptr,obj) do { \
|
if (*(ptr) && !LOAD_VTABLE (*(ptr))) \
|
g_error ("Could not load vtable for obj %p slot %zd (size %zd)", obj, (char*)ptr - (char*)obj, (size_t)safe_object_get_size ((GCObject*)obj)); \
|
} while (0)
|
|
static void
|
check_major_refs_callback (GCObject *obj, size_t size, void *dummy)
|
{
|
char *start = (char*)obj;
|
SgenDescriptor desc = sgen_obj_get_descriptor (obj);
|
|
#include "sgen-scan-object.h"
|
}
|
|
void
|
sgen_check_major_refs (void)
|
{
|
major_collector.iterate_objects (ITERATE_OBJECTS_SWEEP_ALL, (IterateObjectCallbackFunc)check_major_refs_callback, NULL);
|
sgen_los_iterate_objects ((IterateObjectCallbackFunc)check_major_refs_callback, NULL);
|
}
|
|
/* Check that the reference is valid */
|
#undef HANDLE_PTR
|
#define HANDLE_PTR(ptr,obj) do { \
|
if (*(ptr)) { \
|
g_assert (sgen_client_vtable_get_namespace (SGEN_LOAD_VTABLE_UNCHECKED (*(ptr)))); \
|
} \
|
} while (0)
|
|
/*
|
* check_object:
|
*
|
* Perform consistency check on an object. Currently we only check that the
|
* reference fields are valid.
|
*/
|
void
|
check_object (GCObject *obj)
|
{
|
char *start = (char*)obj;
|
SgenDescriptor desc;
|
|
if (!start)
|
return;
|
|
desc = sgen_obj_get_descriptor (obj);
|
|
#include "sgen-scan-object.h"
|
}
|
|
|
static GCObject **valid_nursery_objects;
|
static int valid_nursery_object_count;
|
static gboolean broken_heap;
|
|
static void
|
setup_mono_sgen_scan_area_with_callback (GCObject *object, size_t size, void *data)
|
{
|
valid_nursery_objects [valid_nursery_object_count++] = object;
|
}
|
|
static void
|
setup_valid_nursery_objects (void)
|
{
|
if (!valid_nursery_objects)
|
valid_nursery_objects = (GCObject **)sgen_alloc_os_memory (sgen_nursery_max_size, (SgenAllocFlags)(SGEN_ALLOC_INTERNAL | SGEN_ALLOC_ACTIVATE), "debugging data", MONO_MEM_ACCOUNT_SGEN_DEBUGGING);
|
valid_nursery_object_count = 0;
|
sgen_scan_area_with_callback (nursery_section->data, nursery_section->end_data, setup_mono_sgen_scan_area_with_callback, NULL, FALSE, FALSE);
|
}
|
|
static gboolean
|
find_object_in_nursery_dump (char *object)
|
{
|
int first = 0, last = valid_nursery_object_count;
|
while (first < last) {
|
int middle = first + ((last - first) >> 1);
|
if (object == (char*)valid_nursery_objects [middle])
|
return TRUE;
|
|
if (object < (char*)valid_nursery_objects [middle])
|
last = middle;
|
else
|
first = middle + 1;
|
}
|
g_assert (first == last);
|
return FALSE;
|
}
|
|
static void
|
iterate_valid_nursery_objects (IterateObjectCallbackFunc callback, void *data)
|
{
|
int i;
|
for (i = 0; i < valid_nursery_object_count; ++i) {
|
GCObject *obj = valid_nursery_objects [i];
|
callback (obj, safe_object_get_size (obj), data);
|
}
|
}
|
|
static char*
|
describe_nursery_ptr (char *ptr, gboolean need_setup)
|
{
|
int i;
|
|
if (need_setup)
|
setup_valid_nursery_objects ();
|
|
for (i = 0; i < valid_nursery_object_count - 1; ++i) {
|
if ((char*)valid_nursery_objects [i + 1] > ptr)
|
break;
|
}
|
|
if (i >= valid_nursery_object_count || (char*)valid_nursery_objects [i] + safe_object_get_size (valid_nursery_objects [i]) < ptr) {
|
SGEN_LOG (0, "nursery-ptr (unalloc'd-memory)");
|
return NULL;
|
} else {
|
GCObject *obj = valid_nursery_objects [i];
|
if ((char*)obj == ptr)
|
SGEN_LOG (0, "nursery-ptr %p", obj);
|
else
|
SGEN_LOG (0, "nursery-ptr %p (interior-ptr offset %zd)", obj, ptr - (char*)obj);
|
return (char*)obj;
|
}
|
}
|
|
static gboolean
|
is_valid_object_pointer (char *object)
|
{
|
if (sgen_ptr_in_nursery (object))
|
return find_object_in_nursery_dump (object);
|
|
if (sgen_los_is_valid_object (object))
|
return TRUE;
|
|
if (major_collector.is_valid_object (object))
|
return TRUE;
|
return FALSE;
|
}
|
|
static void
|
bad_pointer_spew (char *obj, char **slot)
|
{
|
char *ptr = *slot;
|
GCVTable vtable = LOAD_VTABLE ((GCObject*)obj);
|
|
SGEN_LOG (0, "Invalid object pointer %p at offset %zd in object %p (%s.%s):", ptr,
|
(char*)slot - obj,
|
obj, sgen_client_vtable_get_namespace (vtable), sgen_client_vtable_get_name (vtable));
|
describe_pointer (ptr, FALSE);
|
broken_heap = TRUE;
|
}
|
|
static void
|
missing_remset_spew (char *obj, char **slot)
|
{
|
char *ptr = *slot;
|
GCVTable vtable = LOAD_VTABLE ((GCObject*)obj);
|
|
SGEN_LOG (0, "Oldspace->newspace reference %p at offset %zd in object %p (%s.%s) not found in remsets.",
|
ptr, (char*)slot - obj, obj,
|
sgen_client_vtable_get_namespace (vtable), sgen_client_vtable_get_name (vtable));
|
|
broken_heap = TRUE;
|
}
|
|
/*
|
FIXME Flag missing remsets due to pinning as non fatal
|
*/
|
#undef HANDLE_PTR
|
#define HANDLE_PTR(ptr,obj) do { \
|
if (*(char**)ptr) { \
|
if (!is_valid_object_pointer (*(char**)ptr)) { \
|
bad_pointer_spew ((char*)obj, (char**)ptr); \
|
} else if (!sgen_ptr_in_nursery (obj) && sgen_ptr_in_nursery ((char*)*ptr)) { \
|
if (!allow_missing_pinned && !SGEN_OBJECT_IS_PINNED (*(ptr)) && !sgen_get_remset ()->find_address ((char*)(ptr)) && !sgen_cement_lookup (*(ptr))) \
|
missing_remset_spew ((char*)obj, (char**)ptr); \
|
} \
|
} \
|
} while (0)
|
|
static void
|
verify_object_pointers_callback (GCObject *obj, size_t size, void *data)
|
{
|
char *start = (char*)obj;
|
gboolean allow_missing_pinned = (gboolean) (size_t) data;
|
SgenDescriptor desc = sgen_obj_get_descriptor_safe (obj);
|
|
#include "sgen-scan-object.h"
|
}
|
|
/*
|
FIXME:
|
-This heap checker is racy regarding inlined write barriers and other JIT tricks that
|
depend on OP_DUMMY_USE.
|
*/
|
void
|
sgen_check_whole_heap (gboolean allow_missing_pinned)
|
{
|
setup_valid_nursery_objects ();
|
|
broken_heap = FALSE;
|
sgen_scan_area_with_callback (nursery_section->data, nursery_section->end_data, verify_object_pointers_callback, (void*) (size_t) allow_missing_pinned, FALSE, TRUE);
|
major_collector.iterate_objects (ITERATE_OBJECTS_SWEEP_ALL, verify_object_pointers_callback, (void*) (size_t) allow_missing_pinned);
|
sgen_los_iterate_objects (verify_object_pointers_callback, (void*) (size_t) allow_missing_pinned);
|
|
g_assert (!broken_heap);
|
}
|
|
static gboolean
|
ptr_in_heap (char *object)
|
{
|
if (sgen_ptr_in_nursery (object))
|
return TRUE;
|
|
if (sgen_los_is_valid_object (object))
|
return TRUE;
|
|
if (major_collector.is_valid_object (object))
|
return TRUE;
|
return FALSE;
|
}
|
|
/*
|
* sgen_check_objref:
|
* Do consistency checks on the object reference OBJ. Assert on failure.
|
*/
|
void
|
sgen_check_objref (char *obj)
|
{
|
g_assert (ptr_in_heap (obj));
|
}
|
|
static void
|
find_pinning_ref_from_thread (char *obj, size_t size)
|
{
|
#ifndef SGEN_WITHOUT_MONO
|
char *endobj = obj + size;
|
|
FOREACH_THREAD (info) {
|
mword *ctxstart, *ctxcurrent, *ctxend;
|
char **start = (char**)info->client_info.stack_start;
|
if (info->client_info.skip || info->client_info.gc_disabled)
|
continue;
|
while (start < (char**)info->client_info.info.stack_end) {
|
if (*start >= obj && *start < endobj)
|
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->client_info.stack_start, info->client_info.info.stack_end);
|
start++;
|
}
|
|
for (ctxstart = ctxcurrent = (mword*) &info->client_info.ctx, ctxend = (mword*) (&info->client_info.ctx + 1); ctxcurrent < ctxend; ctxcurrent ++) {
|
mword w = *ctxcurrent;
|
|
if (w >= (mword)obj && w < (mword)obj + size)
|
SGEN_LOG (0, "Object %p referenced in saved reg %d of thread %p (id %p)", obj, (int) (ctxcurrent - ctxstart), info, (gpointer)mono_thread_info_get_tid (info));
|
}
|
} FOREACH_THREAD_END
|
#endif
|
}
|
|
/*
|
* Debugging function: find in the conservative roots where @obj is being pinned.
|
*/
|
static G_GNUC_UNUSED void
|
find_pinning_reference (char *obj, size_t size)
|
{
|
char **start;
|
RootRecord *root;
|
char *endobj = obj + size;
|
|
SGEN_HASH_TABLE_FOREACH (&roots_hash [ROOT_TYPE_NORMAL], char **, start, RootRecord *, root) {
|
/* if desc is non-null it has precise info */
|
if (!root->root_desc) {
|
while (start < (char**)root->end_root) {
|
if (*start >= obj && *start < endobj) {
|
SGEN_LOG (0, "Object %p referenced in pinned roots %p-%p\n", obj, start, root->end_root);
|
}
|
start++;
|
}
|
}
|
} SGEN_HASH_TABLE_FOREACH_END;
|
|
find_pinning_ref_from_thread (obj, size);
|
}
|
|
#undef HANDLE_PTR
|
#define HANDLE_PTR(ptr,obj) do { \
|
char* __target = *(char**)ptr; \
|
if (__target) { \
|
if (sgen_ptr_in_nursery (__target)) { \
|
g_assert (!SGEN_OBJECT_IS_FORWARDED (__target)); \
|
} else { \
|
mword __size = sgen_safe_object_get_size ((GCObject*)__target); \
|
if (__size <= SGEN_MAX_SMALL_OBJ_SIZE) \
|
g_assert (major_collector.is_object_live ((GCObject*)__target)); \
|
else \
|
g_assert (sgen_los_object_is_pinned ((GCObject*)__target)); \
|
} \
|
} \
|
} while (0)
|
|
static void
|
check_marked_callback (GCObject *obj, size_t size, void *dummy)
|
{
|
char *start = (char*)obj;
|
gboolean flag = (gboolean) (size_t) dummy;
|
SgenDescriptor desc;
|
|
if (sgen_ptr_in_nursery (start)) {
|
if (flag)
|
SGEN_ASSERT (0, SGEN_OBJECT_IS_PINNED (obj), "All objects remaining in the nursery must be pinned");
|
} else if (flag) {
|
if (!sgen_los_object_is_pinned (obj))
|
return;
|
} else {
|
if (!major_collector.is_object_live (obj))
|
return;
|
}
|
|
desc = sgen_obj_get_descriptor_safe (obj);
|
|
#include "sgen-scan-object.h"
|
}
|
|
void
|
sgen_check_heap_marked (gboolean nursery_must_be_pinned)
|
{
|
setup_valid_nursery_objects ();
|
|
iterate_valid_nursery_objects (check_marked_callback, (void*)(size_t)nursery_must_be_pinned);
|
major_collector.iterate_objects (ITERATE_OBJECTS_SWEEP_ALL, check_marked_callback, (void*)FALSE);
|
sgen_los_iterate_objects (check_marked_callback, (void*)TRUE);
|
}
|
|
static void
|
check_nursery_objects_pinned_callback (char *obj, size_t size, void *data /* ScanCopyContext *ctx */)
|
{
|
gboolean pinned = (gboolean) (size_t) data;
|
|
g_assert (!SGEN_OBJECT_IS_FORWARDED (obj));
|
if (pinned)
|
g_assert (SGEN_OBJECT_IS_PINNED (obj));
|
else
|
g_assert (!SGEN_OBJECT_IS_PINNED (obj));
|
}
|
|
void
|
sgen_check_nursery_objects_pinned (gboolean pinned)
|
{
|
sgen_clear_nursery_fragments ();
|
sgen_scan_area_with_callback (nursery_section->data, nursery_section->end_data,
|
(IterateObjectCallbackFunc)check_nursery_objects_pinned_callback, (void*) (size_t) pinned /* (void*)&ctx */, FALSE, TRUE);
|
}
|
|
static void
|
verify_scan_starts (char *start, char *end)
|
{
|
size_t i;
|
|
for (i = 0; i < nursery_section->num_scan_start; ++i) {
|
char *addr = nursery_section->scan_starts [i];
|
if (addr > start && addr < end)
|
SGEN_LOG (0, "NFC-BAD SCAN START [%zu] %p for obj [%p %p]", i, addr, start, end);
|
}
|
}
|
|
void
|
sgen_debug_verify_nursery (gboolean do_dump_nursery_content)
|
{
|
char *start, *end, *cur, *hole_start;
|
|
if (nursery_canaries_enabled ())
|
SGEN_LOG (0, "Checking nursery canaries...");
|
|
/*This cleans up unused fragments */
|
sgen_nursery_allocator_prepare_for_pinning ();
|
|
hole_start = start = cur = sgen_get_nursery_start ();
|
end = sgen_get_nursery_end ();
|
|
while (cur < end) {
|
size_t ss, size;
|
gboolean is_array_fill;
|
|
if (!*(void**)cur) {
|
cur += sizeof (void*);
|
continue;
|
}
|
|
if (object_is_forwarded (cur))
|
SGEN_LOG (0, "FORWARDED OBJ %p", cur);
|
else if (object_is_pinned (cur))
|
SGEN_LOG (0, "PINNED OBJ %p", cur);
|
|
ss = safe_object_get_size ((GCObject*)cur);
|
size = SGEN_ALIGN_UP (ss);
|
verify_scan_starts (cur, cur + size);
|
is_array_fill = sgen_client_object_is_array_fill ((GCObject*)cur);
|
if (do_dump_nursery_content) {
|
GCVTable vtable = SGEN_LOAD_VTABLE ((GCObject*)cur);
|
if (cur > hole_start)
|
SGEN_LOG (0, "HOLE [%p %p %d]", hole_start, cur, (int)(cur - hole_start));
|
SGEN_LOG (0, "OBJ [%p %p %d %d %s.%s %d]", cur, cur + size, (int)size, (int)ss,
|
sgen_client_vtable_get_namespace (vtable), sgen_client_vtable_get_name (vtable),
|
is_array_fill);
|
}
|
if (nursery_canaries_enabled () && !is_array_fill) {
|
CHECK_CANARY_FOR_OBJECT ((GCObject*)cur, TRUE);
|
CANARIFY_SIZE (size);
|
}
|
cur += size;
|
hole_start = cur;
|
}
|
}
|
|
/*
|
* Checks that no objects in the nursery are fowarded or pinned. This
|
* is a precondition to restarting the mutator while doing a
|
* concurrent collection. Note that we don't clear fragments because
|
* we depend on that having happened earlier.
|
*/
|
void
|
sgen_debug_check_nursery_is_clean (void)
|
{
|
char *end, *cur;
|
|
cur = sgen_get_nursery_start ();
|
end = sgen_get_nursery_end ();
|
|
while (cur < end) {
|
size_t size;
|
|
if (!*(void**)cur) {
|
cur += sizeof (void*);
|
continue;
|
}
|
|
g_assert (!object_is_forwarded (cur));
|
g_assert (!object_is_pinned (cur));
|
|
size = SGEN_ALIGN_UP (safe_object_get_size ((GCObject*)cur));
|
verify_scan_starts (cur, cur + size);
|
|
cur += size;
|
}
|
}
|
|
static gboolean scan_object_for_specific_ref_precise = TRUE;
|
|
#undef HANDLE_PTR
|
#define HANDLE_PTR(ptr,obj) do { \
|
if ((GCObject*)*(ptr) == key) { \
|
GCVTable vtable = SGEN_LOAD_VTABLE (*(ptr)); \
|
g_print ("found ref to %p in object %p (%s.%s) at offset %zd\n", \
|
key, (obj), sgen_client_vtable_get_namespace (vtable), sgen_client_vtable_get_name (vtable), ((char*)(ptr) - (char*)(obj))); \
|
} \
|
} while (0)
|
|
static void
|
scan_object_for_specific_ref (GCObject *obj, GCObject *key)
|
{
|
GCObject *forwarded;
|
|
if ((forwarded = SGEN_OBJECT_IS_FORWARDED (obj)))
|
obj = forwarded;
|
|
if (scan_object_for_specific_ref_precise) {
|
char *start = (char*)obj;
|
SgenDescriptor desc = sgen_obj_get_descriptor_safe (obj);
|
#include "sgen-scan-object.h"
|
} else {
|
mword *words = (mword*)obj;
|
size_t size = safe_object_get_size (obj);
|
int i;
|
for (i = 0; i < size / sizeof (mword); ++i) {
|
if (words [i] == (mword)key) {
|
GCVTable vtable = SGEN_LOAD_VTABLE (obj);
|
g_print ("found possible ref to %p in object %p (%s.%s) at offset %zd\n",
|
key, obj, sgen_client_vtable_get_namespace (vtable), sgen_client_vtable_get_name (vtable), i * sizeof (mword));
|
}
|
}
|
}
|
}
|
|
static void
|
scan_object_for_specific_ref_callback (GCObject *obj, size_t size, GCObject *key)
|
{
|
scan_object_for_specific_ref (obj, key);
|
}
|
|
static void
|
check_root_obj_specific_ref (RootRecord *root, GCObject *key, GCObject *obj)
|
{
|
if (key != obj)
|
return;
|
g_print ("found ref to %p in root record %p\n", key, root);
|
}
|
|
static GCObject *check_key = NULL;
|
static RootRecord *check_root = NULL;
|
|
static void
|
check_root_obj_specific_ref_from_marker (GCObject **obj, void *gc_data)
|
{
|
check_root_obj_specific_ref (check_root, check_key, *obj);
|
}
|
|
static void
|
scan_roots_for_specific_ref (GCObject *key, int root_type)
|
{
|
void **start_root;
|
RootRecord *root;
|
check_key = key;
|
|
SGEN_HASH_TABLE_FOREACH (&roots_hash [root_type], void **, start_root, RootRecord *, root) {
|
SgenDescriptor desc = root->root_desc;
|
|
check_root = root;
|
|
switch (desc & ROOT_DESC_TYPE_MASK) {
|
case ROOT_DESC_BITMAP:
|
desc >>= ROOT_DESC_TYPE_SHIFT;
|
while (desc) {
|
if (desc & 1)
|
check_root_obj_specific_ref (root, key, (GCObject *)*start_root);
|
desc >>= 1;
|
start_root++;
|
}
|
return;
|
case ROOT_DESC_COMPLEX: {
|
gsize *bitmap_data = (gsize *)sgen_get_complex_descriptor_bitmap (desc);
|
int bwords = (int) ((*bitmap_data) - 1);
|
void **start_run = start_root;
|
bitmap_data++;
|
while (bwords-- > 0) {
|
gsize bmap = *bitmap_data++;
|
void **objptr = start_run;
|
while (bmap) {
|
if (bmap & 1)
|
check_root_obj_specific_ref (root, key, (GCObject *)*objptr);
|
bmap >>= 1;
|
++objptr;
|
}
|
start_run += GC_BITS_PER_WORD;
|
}
|
break;
|
}
|
case ROOT_DESC_VECTOR: {
|
void **p;
|
|
for (p = start_root; p < (void**)root->end_root; p++) {
|
if (*p)
|
check_root_obj_specific_ref (root, key, (GCObject *)*p);
|
}
|
break;
|
}
|
case ROOT_DESC_USER: {
|
SgenUserRootMarkFunc marker = sgen_get_user_descriptor_func (desc);
|
marker (start_root, check_root_obj_specific_ref_from_marker, NULL);
|
break;
|
}
|
case ROOT_DESC_RUN_LEN:
|
g_assert_not_reached ();
|
default:
|
g_assert_not_reached ();
|
}
|
} SGEN_HASH_TABLE_FOREACH_END;
|
|
check_key = NULL;
|
check_root = NULL;
|
}
|
|
void
|
mono_gc_scan_for_specific_ref (GCObject *key, gboolean precise)
|
{
|
void **ptr;
|
RootRecord *root;
|
|
scan_object_for_specific_ref_precise = precise;
|
|
sgen_scan_area_with_callback (nursery_section->data, nursery_section->end_data,
|
(IterateObjectCallbackFunc)scan_object_for_specific_ref_callback, key, TRUE, FALSE);
|
|
major_collector.iterate_objects (ITERATE_OBJECTS_SWEEP_ALL, (IterateObjectCallbackFunc)scan_object_for_specific_ref_callback, key);
|
|
sgen_los_iterate_objects ((IterateObjectCallbackFunc)scan_object_for_specific_ref_callback, key);
|
|
scan_roots_for_specific_ref (key, ROOT_TYPE_NORMAL);
|
scan_roots_for_specific_ref (key, ROOT_TYPE_WBARRIER);
|
|
SGEN_HASH_TABLE_FOREACH (&roots_hash [ROOT_TYPE_PINNED], void **, ptr, RootRecord *, root) {
|
while (ptr < (void**)root->end_root) {
|
check_root_obj_specific_ref (root, (GCObject *)*ptr, key);
|
++ptr;
|
}
|
} SGEN_HASH_TABLE_FOREACH_END;
|
|
if (sgen_is_world_stopped ())
|
find_pinning_ref_from_thread ((char*)key, sizeof (GCObject));
|
}
|
|
#ifndef SGEN_WITHOUT_MONO
|
|
static MonoDomain *check_domain = NULL;
|
|
static void
|
check_obj_not_in_domain (MonoObject **o)
|
{
|
g_assert (((*o))->vtable->domain != check_domain);
|
}
|
|
|
static void
|
check_obj_not_in_domain_callback (GCObject **o, void *gc_data)
|
{
|
g_assert ((*o)->vtable->domain != check_domain);
|
}
|
|
void
|
sgen_scan_for_registered_roots_in_domain (MonoDomain *domain, int root_type)
|
{
|
void **start_root;
|
RootRecord *root;
|
check_domain = domain;
|
SGEN_HASH_TABLE_FOREACH (&roots_hash [root_type], void **, start_root, RootRecord *, root) {
|
SgenDescriptor desc = root->root_desc;
|
|
/* The MonoDomain struct is allowed to hold
|
references to objects in its own domain. */
|
if (start_root == (void**)domain)
|
continue;
|
|
switch (desc & ROOT_DESC_TYPE_MASK) {
|
case ROOT_DESC_BITMAP:
|
desc >>= ROOT_DESC_TYPE_SHIFT;
|
while (desc) {
|
if ((desc & 1) && *start_root)
|
check_obj_not_in_domain ((MonoObject **)*start_root);
|
desc >>= 1;
|
start_root++;
|
}
|
break;
|
case ROOT_DESC_COMPLEX: {
|
gsize *bitmap_data = (gsize *)sgen_get_complex_descriptor_bitmap (desc);
|
int bwords = (int)((*bitmap_data) - 1);
|
void **start_run = start_root;
|
bitmap_data++;
|
while (bwords-- > 0) {
|
gsize bmap = *bitmap_data++;
|
void **objptr = start_run;
|
while (bmap) {
|
if ((bmap & 1) && *objptr)
|
check_obj_not_in_domain ((MonoObject **)*objptr);
|
bmap >>= 1;
|
++objptr;
|
}
|
start_run += GC_BITS_PER_WORD;
|
}
|
break;
|
}
|
case ROOT_DESC_VECTOR: {
|
void **p;
|
|
for (p = start_root; p < (void**)root->end_root; p++) {
|
if (*p)
|
check_obj_not_in_domain ((MonoObject **)*p);
|
}
|
break;
|
}
|
case ROOT_DESC_USER: {
|
SgenUserRootMarkFunc marker = sgen_get_user_descriptor_func (desc);
|
marker (start_root, check_obj_not_in_domain_callback, NULL);
|
break;
|
}
|
case ROOT_DESC_RUN_LEN:
|
g_assert_not_reached ();
|
default:
|
g_assert_not_reached ();
|
}
|
} SGEN_HASH_TABLE_FOREACH_END;
|
|
check_domain = NULL;
|
}
|
|
static gboolean
|
is_xdomain_ref_allowed (GCObject **ptr, GCObject *obj, MonoDomain *domain)
|
{
|
MonoObject *o = (MonoObject*)(obj);
|
MonoObject *ref = *ptr;
|
size_t offset = (char*)(ptr) - (char*)o;
|
|
if (o->vtable->klass == mono_defaults.thread_class && offset == G_STRUCT_OFFSET (MonoThread, internal_thread))
|
return TRUE;
|
if (o->vtable->klass == mono_defaults.internal_thread_class && offset == G_STRUCT_OFFSET (MonoInternalThread, current_appcontext))
|
return TRUE;
|
|
#ifndef DISABLE_REMOTING
|
if (mono_defaults.real_proxy_class->supertypes && mono_class_has_parent_fast (o->vtable->klass, mono_defaults.real_proxy_class) &&
|
offset == G_STRUCT_OFFSET (MonoRealProxy, unwrapped_server))
|
return TRUE;
|
#endif
|
/*
|
* at System.IO.MemoryStream.InternalConstructor (byte[],int,int,bool,bool) [0x0004d] in /home/schani/Work/novell/trunk/mcs/class/corlib/System.IO/MemoryStream.cs:121
|
* at System.IO.MemoryStream..ctor (byte[]) [0x00017] in /home/schani/Work/novell/trunk/mcs/class/corlib/System.IO/MemoryStream.cs:81
|
* at (wrapper remoting-invoke-with-check) System.IO.MemoryStream..ctor (byte[]) <IL 0x00020, 0xffffffff>
|
* at System.Runtime.Remoting.Messaging.CADMethodCallMessage.GetArguments () [0x0000d] in /home/schani/Work/novell/trunk/mcs/class/corlib/System.Runtime.Remoting.Messaging/CADMessages.cs:327
|
* at System.Runtime.Remoting.Messaging.MethodCall..ctor (System.Runtime.Remoting.Messaging.CADMethodCallMessage) [0x00017] in /home/schani/Work/novell/trunk/mcs/class/corlib/System.Runtime.Remoting.Messaging/MethodCall.cs:87
|
* at System.AppDomain.ProcessMessageInDomain (byte[],System.Runtime.Remoting.Messaging.CADMethodCallMessage,byte[]&,System.Runtime.Remoting.Messaging.CADMethodReturnMessage&) [0x00018] in /home/schani/Work/novell/trunk/mcs/class/corlib/System/AppDomain.cs:1213
|
* at (wrapper remoting-invoke-with-check) System.AppDomain.ProcessMessageInDomain (byte[],System.Runtime.Remoting.Messaging.CADMethodCallMessage,byte[]&,System.Runtime.Remoting.Messaging.CADMethodReturnMessage&) <IL 0x0003d, 0xffffffff>
|
* at System.Runtime.Remoting.Channels.CrossAppDomainSink.ProcessMessageInDomain (byte[],System.Runtime.Remoting.Messaging.CADMethodCallMessage) [0x00008] in /home/schani/Work/novell/trunk/mcs/class/corlib/System.Runtime.Remoting.Channels/CrossAppDomainChannel.cs:198
|
* at (wrapper runtime-invoke) object.runtime_invoke_CrossAppDomainSink/ProcessMessageRes_object_object (object,intptr,intptr,intptr) <IL 0x0004c, 0xffffffff>
|
*/
|
if (!strcmp (ref->vtable->klass->name_space, "System") &&
|
!strcmp (ref->vtable->klass->name, "Byte[]") &&
|
!strcmp (o->vtable->klass->name_space, "System.IO") &&
|
!strcmp (o->vtable->klass->name, "MemoryStream"))
|
return TRUE;
|
return FALSE;
|
}
|
|
static void
|
check_reference_for_xdomain (GCObject **ptr, GCObject *obj, MonoDomain *domain)
|
{
|
MonoObject *ref = *ptr;
|
size_t offset = (char*)(ptr) - (char*)obj;
|
MonoClass *klass;
|
MonoClassField *field;
|
char *str;
|
|
if (!ref || ref->vtable->domain == domain)
|
return;
|
if (is_xdomain_ref_allowed (ptr, obj, domain))
|
return;
|
|
field = NULL;
|
for (klass = obj->vtable->klass; klass; klass = klass->parent) {
|
int i;
|
|
int fcount = mono_class_get_field_count (klass);
|
for (i = 0; i < fcount; ++i) {
|
if (klass->fields[i].offset == offset) {
|
field = &klass->fields[i];
|
break;
|
}
|
}
|
if (field)
|
break;
|
}
|
|
if (ref->vtable->klass == mono_defaults.string_class) {
|
MonoError error;
|
str = mono_string_to_utf8_checked ((MonoString*)ref, &error);
|
mono_error_cleanup (&error);
|
} else
|
str = NULL;
|
g_print ("xdomain reference in %p (%s.%s) at offset %d (%s) to %p (%s.%s) (%s) - pointed to by:\n",
|
obj, obj->vtable->klass->name_space, obj->vtable->klass->name,
|
offset, field ? field->name : "",
|
ref, ref->vtable->klass->name_space, ref->vtable->klass->name, str ? str : "");
|
mono_gc_scan_for_specific_ref (obj, TRUE);
|
if (str)
|
g_free (str);
|
}
|
|
#undef HANDLE_PTR
|
#define HANDLE_PTR(ptr,obj) check_reference_for_xdomain ((ptr), (obj), domain)
|
|
static void
|
scan_object_for_xdomain_refs (GCObject *obj, mword size, void *data)
|
{
|
char *start = (char*)obj;
|
MonoVTable *vt = SGEN_LOAD_VTABLE (obj);
|
MonoDomain *domain = vt->domain;
|
SgenDescriptor desc = sgen_vtable_get_descriptor (vt);
|
|
#include "sgen-scan-object.h"
|
}
|
|
void
|
sgen_check_for_xdomain_refs (void)
|
{
|
LOSObject *bigobj;
|
|
sgen_scan_area_with_callback (nursery_section->data, nursery_section->end_data,
|
(IterateObjectCallbackFunc)scan_object_for_xdomain_refs, NULL, FALSE, TRUE);
|
|
major_collector.iterate_objects (ITERATE_OBJECTS_SWEEP_ALL, (IterateObjectCallbackFunc)scan_object_for_xdomain_refs, NULL);
|
|
for (bigobj = los_object_list; bigobj; bigobj = bigobj->next)
|
scan_object_for_xdomain_refs ((GCObject*)bigobj->data, sgen_los_object_size (bigobj), NULL);
|
}
|
|
#endif
|
|
/* If not null, dump the heap after each collection into this file */
|
static FILE *heap_dump_file = NULL;
|
|
void
|
sgen_dump_occupied (char *start, char *end, char *section_start)
|
{
|
fprintf (heap_dump_file, "<occupied offset=\"%zd\" size=\"%zd\"/>\n", start - section_start, end - start);
|
}
|
|
void
|
sgen_dump_section (GCMemSection *section, const char *type)
|
{
|
char *start = section->data;
|
char *end = section->end_data;
|
char *occ_start = NULL;
|
|
fprintf (heap_dump_file, "<section type=\"%s\" size=\"%lu\">\n", type, (unsigned long)(section->end_data - section->data));
|
|
while (start < end) {
|
guint size;
|
//GCVTable vt;
|
//MonoClass *class;
|
|
if (!*(void**)start) {
|
if (occ_start) {
|
sgen_dump_occupied (occ_start, start, section->data);
|
occ_start = NULL;
|
}
|
start += sizeof (void*); /* should be ALLOC_ALIGN, really */
|
continue;
|
}
|
|
if (!occ_start)
|
occ_start = start;
|
|
//vt = SGEN_LOAD_VTABLE (start);
|
//class = vt->klass;
|
|
size = SGEN_ALIGN_UP (safe_object_get_size ((GCObject*) start));
|
|
/*
|
fprintf (heap_dump_file, "<object offset=\"%d\" class=\"%s.%s\" size=\"%d\"/>\n",
|
start - section->data,
|
vt->klass->name_space, vt->klass->name,
|
size);
|
*/
|
|
start += size;
|
}
|
if (occ_start)
|
sgen_dump_occupied (occ_start, start, section->data);
|
|
fprintf (heap_dump_file, "</section>\n");
|
}
|
|
static void
|
dump_object (GCObject *obj, gboolean dump_location)
|
{
|
#ifndef SGEN_WITHOUT_MONO
|
static char class_name [1024];
|
|
MonoClass *klass = mono_object_class (obj);
|
int i, j;
|
|
/*
|
* Python's XML parser is too stupid to parse angle brackets
|
* in strings, so we just ignore them;
|
*/
|
i = j = 0;
|
while (klass->name [i] && j < sizeof (class_name) - 1) {
|
if (!strchr ("<>\"", klass->name [i]))
|
class_name [j++] = klass->name [i];
|
++i;
|
}
|
g_assert (j < sizeof (class_name));
|
class_name [j] = 0;
|
|
fprintf (heap_dump_file, "<object class=\"%s.%s\" size=\"%zd\"",
|
klass->name_space, class_name,
|
safe_object_get_size (obj));
|
if (dump_location) {
|
const char *location;
|
if (sgen_ptr_in_nursery (obj))
|
location = "nursery";
|
else if (safe_object_get_size (obj) <= SGEN_MAX_SMALL_OBJ_SIZE)
|
location = "major";
|
else
|
location = "LOS";
|
fprintf (heap_dump_file, " location=\"%s\"", location);
|
}
|
fprintf (heap_dump_file, "/>\n");
|
#endif
|
}
|
|
void
|
sgen_debug_enable_heap_dump (const char *filename)
|
{
|
heap_dump_file = fopen (filename, "w");
|
if (heap_dump_file) {
|
fprintf (heap_dump_file, "<sgen-dump>\n");
|
sgen_pin_stats_enable ();
|
}
|
}
|
|
void
|
sgen_debug_dump_heap (const char *type, int num, const char *reason)
|
{
|
SgenPointerQueue *pinned_objects;
|
LOSObject *bigobj;
|
int i;
|
|
if (!heap_dump_file)
|
return;
|
|
fprintf (heap_dump_file, "<collection type=\"%s\" num=\"%d\"", type, num);
|
if (reason)
|
fprintf (heap_dump_file, " reason=\"%s\"", reason);
|
fprintf (heap_dump_file, ">\n");
|
#ifndef SGEN_WITHOUT_MONO
|
fprintf (heap_dump_file, "<other-mem-usage type=\"mempools\" size=\"%ld\"/>\n", mono_mempool_get_bytes_allocated ());
|
#endif
|
sgen_dump_internal_mem_usage (heap_dump_file);
|
fprintf (heap_dump_file, "<pinned type=\"stack\" bytes=\"%zu\"/>\n", sgen_pin_stats_get_pinned_byte_count (PIN_TYPE_STACK));
|
/* fprintf (heap_dump_file, "<pinned type=\"static-data\" bytes=\"%d\"/>\n", pinned_byte_counts [PIN_TYPE_STATIC_DATA]); */
|
fprintf (heap_dump_file, "<pinned type=\"other\" bytes=\"%zu\"/>\n", sgen_pin_stats_get_pinned_byte_count (PIN_TYPE_OTHER));
|
|
fprintf (heap_dump_file, "<pinned-objects>\n");
|
pinned_objects = sgen_pin_stats_get_object_list ();
|
for (i = 0; i < pinned_objects->next_slot; ++i)
|
dump_object ((GCObject *)pinned_objects->data [i], TRUE);
|
fprintf (heap_dump_file, "</pinned-objects>\n");
|
|
sgen_dump_section (nursery_section, "nursery");
|
|
major_collector.dump_heap (heap_dump_file);
|
|
fprintf (heap_dump_file, "<los>\n");
|
for (bigobj = los_object_list; bigobj; bigobj = bigobj->next)
|
dump_object ((GCObject*)bigobj->data, FALSE);
|
fprintf (heap_dump_file, "</los>\n");
|
|
fprintf (heap_dump_file, "</collection>\n");
|
}
|
|
static GCObject *found_obj;
|
|
static void
|
find_object_for_ptr_callback (GCObject *obj, size_t size, void *user_data)
|
{
|
char *ptr = (char *)user_data;
|
|
if (ptr >= (char*)obj && ptr < (char*)obj + size) {
|
g_assert (!found_obj);
|
found_obj = obj;
|
}
|
}
|
|
/* for use in the debugger */
|
GCObject*
|
sgen_find_object_for_ptr (char *ptr)
|
{
|
if (ptr >= nursery_section->data && ptr < nursery_section->end_data) {
|
found_obj = NULL;
|
sgen_scan_area_with_callback (nursery_section->data, nursery_section->end_data,
|
find_object_for_ptr_callback, ptr, TRUE, FALSE);
|
if (found_obj)
|
return found_obj;
|
}
|
|
found_obj = NULL;
|
sgen_los_iterate_objects (find_object_for_ptr_callback, ptr);
|
if (found_obj)
|
return found_obj;
|
|
/*
|
* Very inefficient, but this is debugging code, supposed to
|
* be called from gdb, so we don't care.
|
*/
|
found_obj = NULL;
|
major_collector.iterate_objects (ITERATE_OBJECTS_SWEEP_ALL, find_object_for_ptr_callback, ptr);
|
return found_obj;
|
}
|
|
#endif /*HAVE_SGEN_GC*/
|