Skip to content

Commit 562896c

Browse files
ChrisJeffersonfingolfin
authored andcommitted
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 71b6290 commit 562896c

File tree

8 files changed

+289
-9
lines changed

8 files changed

+289
-9
lines changed

configure.ac

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

241+
AC_ARG_ENABLE([memory-checking],
242+
[AS_HELP_STRING([--enable-memory-checking], [enable memory checking])],
243+
[AC_DEFINE([GAP_MEM_CHECK], [1], [define if building with memory checking])],
244+
[enable_memory_checking=no]
245+
)
246+
AC_MSG_CHECKING([whether to enable memory checking])
247+
AC_MSG_RESULT([$enable_memory_checking])
248+
241249
dnl
242250
dnl User setting: Enable -Werror (off by default)
243251
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: 136 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,113 @@ 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+
static void MaybeMoveBags(void)
1051+
{
1052+
static Int oldBase = 0;
1053+
1054+
if (!EnableMemCheck)
1055+
return;
1056+
1057+
Int newBase = oldBase + 1;
1058+
// Memory buffer 0 is special, as we use that
1059+
// copy for the master pointers. Therefore never
1060+
// block access to it, and skip it when cycling.
1061+
if (newBase >= GetMembufCount())
1062+
newBase = 1;
1063+
1064+
// call the before function (if any)
1065+
if (BeforeCollectFuncBags != 0)
1066+
(*BeforeCollectFuncBags)();
1067+
1068+
MoveBagMemory(GetMembuf(oldBase), GetMembuf(newBase));
1069+
1070+
// Enable access to new memory
1071+
mprotect(GetMembuf(newBase), GetMembufSize(), PROT_READ | PROT_WRITE);
1072+
// Block access to old memory (except block 0, which will only occur
1073+
// on the first call).
1074+
if (oldBase != 0) {
1075+
mprotect(GetMembuf(oldBase), GetMembufSize(), PROT_NONE);
1076+
}
1077+
1078+
// call the after function (if any)
1079+
if (AfterCollectFuncBags != 0)
1080+
(*AfterCollectFuncBags)();
1081+
1082+
1083+
oldBase = newBase;
1084+
}
1085+
1086+
#endif
9761087

9771088
/****************************************************************************
9781089
**
@@ -1032,17 +1143,24 @@ void InitBags (
10321143
SyAbortBags("cannot get storage for the initial workspace.");
10331144
EndBags = MptrBags + 1024*(initial_size / sizeof(Bag*));
10341145

1146+
// In GAP_MEM_CHECK we want as few master pointers as possible, as we
1147+
// have to loop over them very frequently.
1148+
#ifdef GAP_MEM_CHECK
1149+
UInt initialBagCount = 100000;
1150+
#else
1151+
UInt initialBagCount = 1024*initial_size/8/sizeof(Bag*);
1152+
#endif
10351153
/* 1/8th of the storage goes into the masterpointer area */
10361154
FreeMptrBags = (Bag)MptrBags;
10371155
for ( p = MptrBags;
1038-
p + 2*(SIZE_MPTR_BAGS) <= MptrBags+1024*initial_size/8/sizeof(Bag*);
1156+
p + 2*(SIZE_MPTR_BAGS) <= MptrBags+initialBagCount;
10391157
p += SIZE_MPTR_BAGS )
10401158
{
10411159
*p = (Bag)(p + SIZE_MPTR_BAGS);
10421160
}
10431161

10441162
/* the rest is for bags */
1045-
MptrEndBags = MptrBags + 1024*initial_size/8/sizeof(Bag*);
1163+
MptrEndBags = MptrBags + initialBagCount;
10461164
// Add a small gap between the end of the master pointers and OldBags
10471165
// This is mainly here to ensure we do not break allowing OldBags and
10481166
// MptrEndBags to differ.
@@ -1104,6 +1222,10 @@ Bag NewBag (
11041222
{
11051223
Bag bag; /* identifier of the new bag */
11061224

1225+
#ifdef GAP_MEM_CHECK
1226+
MaybeMoveBags();
1227+
#endif
1228+
11071229
#ifdef TREMBLE_HEAP
11081230
CollectBags(0,0);
11091231
#endif
@@ -1269,6 +1391,11 @@ UInt ResizeBag (
12691391
Bag bag,
12701392
UInt new_size )
12711393
{
1394+
1395+
#ifdef GAP_MEM_CHECK
1396+
MaybeMoveBags();
1397+
#endif
1398+
12721399
#ifdef TREMBLE_HEAP
12731400
CollectBags(0,0);
12741401
#endif
@@ -2064,12 +2191,15 @@ UInt CollectBags (
20642191
&& SyAllocBags(-512,0) )
20652192
EndBags -= WORDS_BAG(512*1024L);
20662193

2194+
#ifdef GAP_MEM_CHECK
2195+
UInt SpareMasterPointers = 100000;
2196+
#else
2197+
UInt SpareMasterPointers = SpaceBetweenPointers(EndBags, stopBags)/7;
2198+
#endif
20672199
/* if we want to increase the masterpointer area */
2068-
if ( SizeMptrsArea-NrLiveBags < SpaceBetweenPointers(EndBags,stopBags)/7 ) {
2069-
2200+
if ( SizeMptrsArea-NrLiveBags < SpareMasterPointers ) {
20702201
/* this is how many new masterpointers we want */
2071-
i = SpaceBetweenPointers(EndBags,stopBags)/7 - (SizeMptrsArea-NrLiveBags);
2072-
2202+
i = SpareMasterPointers - (SizeMptrsArea-NrLiveBags);
20732203
/* move the bags area */
20742204
SyMemmove(OldBags+i, OldBags, SizeAllBagsArea*sizeof(*OldBags));
20752205

src/gasman.h

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

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

0 commit comments

Comments
 (0)