Skip to content

Commit 1c33c19

Browse files
Add memory debugging
This adds a new configure option to GAP which enables an extremely slow debug mode, which can find problems where references to memory are incorrectly kept across garbage collections.
1 parent e695a9c commit 1c33c19

File tree

7 files changed

+279
-9
lines changed

7 files changed

+279
-9
lines changed

configure.ac

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,14 @@ AC_ARG_ENABLE([debug],
207207
AC_MSG_CHECKING([whether to enable debug mode])
208208
AC_MSG_RESULT([$enable_debug])
209209

210+
AC_ARG_ENABLE([memory-checking],
211+
[AS_HELP_STRING([--enable-memory-checking], [enable memory checking])],
212+
[AC_DEFINE([GAP_MEM_CHECK], [1], [define if building with memory checking])],
213+
[enable_memory_checking=no]
214+
)
215+
AC_MSG_CHECKING([whether to enable memory checking])
216+
AC_MSG_RESULT([$enable_memory_checking])
217+
210218
dnl
211219
dnl User setting: Enable -Werror (off by default)
212220
dnl

lib/init.g

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,9 @@ BindGlobal( "ShowKernelInformation", function()
560560
if GAPInfo.KernelInfo.KernelDebug then
561561
Add( config, "KernelDebug" );
562562
fi;
563+
if GAPInfo.KernelInfo.MemCheck then
564+
Add(config, "MemCheck");
565+
fi;
563566
if config <> [] then
564567
print_info( " Configuration: ", config, "\n" );
565568
fi;

lib/system.g

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,8 @@ BIND_GLOBAL( "GAPInfo", rec(
110110
help := [ "Run ProfileLineByLine(<filename>) with recordMem := true on GAP start"] ),
111111
rec( long := "cover", default := "", arg := "<file>",
112112
help := [ "Run CoverageLineByLine(<filename>) on GAP start"] ),
113-
],
113+
rec( long := "enableMemCheck", default := false)
114+
],
114115
) );
115116

116117

src/gap.c

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1882,6 +1882,18 @@ Obj FuncGASMAN_LIMITS( Obj self )
18821882
return list;
18831883
}
18841884

1885+
#ifdef GAP_MEM_CHECK
1886+
1887+
extern Int EnableMemCheck;
1888+
1889+
Obj FuncGASMAN_MEM_CHECK(Obj self, Obj newval)
1890+
{
1891+
EnableMemCheck = INT_INTOBJ(newval);
1892+
return 0;
1893+
}
1894+
1895+
#endif
1896+
18851897
/****************************************************************************
18861898
**
18871899
*F FuncTotalMemoryAllocated( <self> ) .expert function 'TotalMemoryAllocated'
@@ -2674,6 +2686,13 @@ Obj FuncKERNEL_INFO(Obj self) {
26742686
AssPRec(res, r, False);
26752687
#endif
26762688

2689+
r = RNamName("MemCheck");
2690+
#ifdef GAP_MEM_CHECK
2691+
AssPRec(res, r, True);
2692+
#else
2693+
AssPRec(res, r, False);
2694+
#endif
2695+
26772696
MakeImmutable(res);
26782697

26792698
return res;
@@ -2769,7 +2788,7 @@ void ThreadedInterpreter(void *funcargs) {
27692788
**
27702789
*V GVarFuncs . . . . . . . . . . . . . . . . . . list of functions to export
27712790
*/
2772-
static StructGVarFunc GVarFuncs [] = {
2791+
static StructGVarFunc GVarFuncs[] = {
27732792

27742793
GVAR_FUNC(Runtime, 0, ""),
27752794
GVAR_FUNC(RUNTIMES, 0, ""),
@@ -2790,6 +2809,9 @@ static StructGVarFunc GVarFuncs [] = {
27902809
GVAR_FUNC(GASMAN_STATS, 0, ""),
27912810
GVAR_FUNC(GASMAN_MESSAGE_STATUS, 0, ""),
27922811
GVAR_FUNC(GASMAN_LIMITS, 0, ""),
2812+
#ifdef GAP_MEM_CHECK
2813+
GVAR_FUNC(GASMAN_MEM_CHECK, 1, "int"),
2814+
#endif
27932815
GVAR_FUNC(TotalMemoryAllocated, 0, ""),
27942816
GVAR_FUNC(SIZE_OBJ, 1, "object"),
27952817
GVAR_FUNC(TNUM_OBJ, 1, "object"),
@@ -2804,7 +2826,8 @@ static StructGVarFunc GVarFuncs [] = {
28042826
GVAR_FUNC(QUIT_GAP, -1, "args"),
28052827
GVAR_FUNC(FORCE_QUIT_GAP, -1, "args"),
28062828
GVAR_FUNC(SHOULD_QUIT_ON_BREAK, 0, ""),
2807-
GVAR_FUNC(SHELL, -1, "context, canReturnVoid, canReturnObj, lastDepth, setTime, prompt, promptHook, infile, outfile"),
2829+
GVAR_FUNC(SHELL, -1, "context, canReturnVoid, canReturnObj, lastDepth, "
2830+
"setTime, prompt, promptHook, infile, outfile"),
28082831
GVAR_FUNC(CALL_WITH_CATCH, 2, "func, args"),
28092832
GVAR_FUNC(JUMP_TO_CATCH, 1, "payload"),
28102833
GVAR_FUNC(KERNEL_INFO, 0, ""),

src/gasman.c

Lines changed: 142 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@
119119

120120
#include <stdio.h>
121121

122+
#ifdef GAP_MEM_CHECK
123+
#include <sys/mman.h>
124+
#endif
125+
122126
/****************************************************************************
123127
**
124128
*F WORDS_BAG( <size> ) . . . . . . . . . . words used by a bag of given size
@@ -973,6 +977,119 @@ void InitCollectFuncBags (
973977
AfterCollectFuncBags = after_func;
974978
}
975979

980+
/***************************************************************
981+
* GAP_MEM_CHECK
982+
*
983+
* One of the hardest categories of bugs to fix in GAP are where
984+
* a reference to the internals of a GAP object are kept across
985+
* a garbage collection (which moves GAP objects around).
986+
*
987+
* GAP_MEM_CHECK provides a method of detecting such problems, at
988+
* the cost of GREATLY decreased performance (Starting GAP in
989+
* --enableMemCheck mode takes days, rather than seconds).
990+
*
991+
* The fundamental idea behind GAP_MEM_CHECK is, whenever NewBag
992+
* or ResizeBag is called, then the contents of every Bag in
993+
* GAP is moved, and the memory previously being used is marked
994+
* as not readable or writable using 'mprotect'.
995+
*
996+
* Actually copying all GAP's memory space would be extremely
997+
* expensive, so instead we use 'mmap' to set up a set of copies
998+
* of the GAP memory space, which are represented by the same
999+
* underlying physical memory.
1000+
*
1001+
* The 0th such copy (which we also ensure is the one at the
1002+
* lowest memory address) is special -- this is where we
1003+
* reference the master pointers (which can't move). We do not
1004+
* 'mprotect' any of this memory.
1005+
*
1006+
* Every time we call 'NewBag' or 'ResizeBag', we change which
1007+
* copy of the GASMAN memory space the master pointers point
1008+
* to, disabling access to the previous copy, and enabling access
1009+
* to the new one.
1010+
*
1011+
* We never use the master pointers in any copy other than the
1012+
* 0th, and we never refer to the Bag area in the 0th copy. However,
1013+
* it simplifies things to not try to seperate the master pointer
1014+
* and Bag areas, because the master pointer area can grow as GAP
1015+
* runs.
1016+
*
1017+
* Because this code is VERY slow, it can be turned on and off.
1018+
* At run time, call GASMAN_MEM_CHECK(1) to enable, and
1019+
* GASMAN_MEM_CHECK(0) to disable. Start GAP with --enableMemCheck
1020+
* to enable from when GAP starts.
1021+
*/
1022+
1023+
#ifdef GAP_MEM_CHECK
1024+
1025+
Int EnableMemCheck = 0;
1026+
1027+
Int enableMemCheck(Char ** argv, void * dummy)
1028+
{
1029+
SyFputs( "# Warning: --enableMemCheck causes SEVERE slowdowns. Starting GAP may take several days!\n", 3 );
1030+
EnableMemCheck = 1;
1031+
return 1;
1032+
}
1033+
1034+
static void MoveBagMemory(char * oldbase, char * newbase)
1035+
{
1036+
Int moveSize = (newbase - oldbase) / sizeof(Bag);
1037+
// update the masterpointers
1038+
for (Bag * p = MptrBags; p < MptrEndBags; p++) {
1039+
if ((Bag)MptrEndBags <= *p)
1040+
*p += moveSize;
1041+
}
1042+
1043+
// update 'OldBags', 'YoungBags', 'AllocBags', and 'EndBags'
1044+
OldBags += moveSize;
1045+
YoungBags += moveSize;
1046+
AllocBags += moveSize;
1047+
EndBags += moveSize;
1048+
}
1049+
1050+
1051+
// Reference functions from system.c
1052+
1053+
UInt GetMembufCount(void);
1054+
void * GetMembuf(UInt i);
1055+
UInt GetMembufSize(void);
1056+
1057+
static void MaybeMoveBags(void)
1058+
{
1059+
static Int oldBase = 0;
1060+
1061+
if (!EnableMemCheck)
1062+
return;
1063+
1064+
Int newBase = oldBase + 1;
1065+
// Memory buffer 0 is special, as we use that
1066+
// copy for the master pointers. Therefore never
1067+
// block access to it, and skip it when cycling.
1068+
if (newBase >= GetMembufCount())
1069+
newBase = 1;
1070+
1071+
// call the before function (if any)
1072+
if (BeforeCollectFuncBags != 0)
1073+
(*BeforeCollectFuncBags)();
1074+
1075+
MoveBagMemory(GetMembuf(oldBase), GetMembuf(newBase));
1076+
1077+
// Enable access to new memory
1078+
mprotect(GetMembuf(newBase), GetMembufSize(), PROT_READ | PROT_WRITE);
1079+
// Block access to old memory (except block 0, which will only occur
1080+
// on the first call).
1081+
if (oldBase != 0) {
1082+
mprotect(GetMembuf(oldBase), GetMembufSize(), PROT_NONE);
1083+
}
1084+
1085+
// call the after function (if any)
1086+
if (AfterCollectFuncBags != 0)
1087+
(*AfterCollectFuncBags)();
1088+
1089+
1090+
oldBase = newBase;
1091+
}
1092+
#endif
9761093

9771094
/****************************************************************************
9781095
**
@@ -1032,17 +1149,24 @@ void InitBags (
10321149
SyAbortBags("cannot get storage for the initial workspace.");
10331150
EndBags = MptrBags + 1024*(initial_size / sizeof(Bag*));
10341151

1152+
// In GAP_MEM_CHECK we want as few master pointers as possible, as we
1153+
// have to loop over them very frequently.
1154+
#ifdef GAP_MEM_CHECK
1155+
UInt initialBagCount = 100000;
1156+
#else
1157+
UInt initialBagCount = 1024*initial_size/8/sizeof(Bag*);
1158+
#endif
10351159
/* 1/8th of the storage goes into the masterpointer area */
10361160
FreeMptrBags = (Bag)MptrBags;
10371161
for ( p = MptrBags;
1038-
p + 2*(SIZE_MPTR_BAGS) <= MptrBags+1024*initial_size/8/sizeof(Bag*);
1162+
p + 2*(SIZE_MPTR_BAGS) <= MptrBags+initialBagCount;
10391163
p += SIZE_MPTR_BAGS )
10401164
{
10411165
*p = (Bag)(p + SIZE_MPTR_BAGS);
10421166
}
10431167

10441168
/* the rest is for bags */
1045-
MptrEndBags = MptrBags + 1024*initial_size/8/sizeof(Bag*);
1169+
MptrEndBags = MptrBags + initialBagCount;
10461170
// Add a small gap between the end of the master pointers and OldBags
10471171
// This is mainly here to ensure we do not break allowing OldBags and
10481172
// MptrEndBags to differ.
@@ -1104,6 +1228,10 @@ Bag NewBag (
11041228
{
11051229
Bag bag; /* identifier of the new bag */
11061230

1231+
#ifdef GAP_MEM_CHECK
1232+
MaybeMoveBags();
1233+
#endif
1234+
11071235
#ifdef TREMBLE_HEAP
11081236
CollectBags(0,0);
11091237
#endif
@@ -1269,6 +1397,11 @@ UInt ResizeBag (
12691397
Bag bag,
12701398
UInt new_size )
12711399
{
1400+
1401+
#ifdef GAP_MEM_CHECK
1402+
MaybeMoveBags();
1403+
#endif
1404+
12721405
#ifdef TREMBLE_HEAP
12731406
CollectBags(0,0);
12741407
#endif
@@ -2064,12 +2197,15 @@ UInt CollectBags (
20642197
&& SyAllocBags(-512,0) )
20652198
EndBags -= WORDS_BAG(512*1024L);
20662199

2200+
#if GAP_MEM_CHECK
2201+
UInt SpareMasterPointers = 100000;
2202+
#else
2203+
UInt SpareMasterPointers = SpaceBetweenPointers(EndBags, stopBags)/7;
2204+
#endif
20672205
/* if we want to increase the masterpointer area */
2068-
if ( SizeMptrsArea-NrLiveBags < SpaceBetweenPointers(EndBags,stopBags)/7 ) {
2069-
2206+
if ( SizeMptrsArea-NrLiveBags < SpareMasterPointers ) {
20702207
/* this is how many new masterpointers we want */
2071-
i = SpaceBetweenPointers(EndBags,stopBags)/7 - (SizeMptrsArea-NrLiveBags);
2072-
2208+
i = SpareMasterPointers - (SizeMptrsArea-NrLiveBags);
20732209
/* move the bags area */
20742210
SyMemmove(OldBags+i, OldBags, SizeAllBagsArea*sizeof(*OldBags));
20752211

src/gasman.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1144,4 +1144,8 @@ extern void CallbackForAllBags(
11441144
void *AllocateMemoryBlock(UInt size);
11451145
#endif
11461146

1147+
#ifdef GAP_MEM_CHECK
1148+
Int enableMemCheck(Char ** argv, void * dummy);
1149+
#endif
1150+
11471151
#endif // GAP_GASMAN_H

0 commit comments

Comments
 (0)