(FBMLD) FreeBASIC Memory Leak Detector

Post your FreeBASIC source, examples, tips and tricks here. Please don’t post code without including an explanation.
DrV
Site Admin
Posts: 2116
Joined: May 27, 2005 18:39
Location: Midwestern USA
Contact:

(FBMLD) FreeBASIC Memory Leak Detector

Post by DrV »

FBMLD replaces the built-in *allocate functions and produces a report of memory that hasn't been freed at the end of the program and notifies you immediately when attempting to double-free or free a pointer that wasn't returned from an *allocate function.

Use:

Code: Select all

#include "fbmld.bi"

' use *allocate as normal
If you don't want to link to the multithreaded runtime:

Code: Select all

#define FBMLD_NO_MULTITHREADING
#include "fbmld.bi"

fbmld.bi:

Code: Select all

''
'' fbmld (FB Memory Leak Detector) version 0.6
'' Copyright (C) 2006 Daniel R. Verkamp
'' Tree storage implemented by yetifoot
''
'' This software is provided 'as-is', without any express or implied warranty. 
'' In no event will the authors be held liable for any damages arising from 
'' the use of this software.
'' 
'' Permission is granted to anyone to use this software for any purpose, 
'' including commercial applications, and to alter it and redistribute it 
'' freely, subject to the following restrictions:
'' 
'' 1. The origin of this software must not be misrepresented; you must not claim 
'' that you wrote the original software. If you use this software in a product, 
'' an acknowledgment in the product documentation would be appreciated but is 
'' not required.
'' 
'' 2. Altered source versions must be plainly marked as such, and must not be 
'' misrepresented as being the original software.
'' 
'' 3. This notice may not be removed or altered from any source 
'' distribution. 
''

#ifndef __FBMLD__
#define __FBMLD__

#include "crt.bi"

#undef allocate
#undef callocate
#undef reallocate
#undef deallocate

#define allocate(bytes) fbmld_allocate((bytes), __FILE__, __LINE__)
#define callocate(bytes) fbmld_callocate((bytes), __FILE__, __LINE__)
#define reallocate(pt, bytes) fbmld_reallocate((pt), (bytes), __FILE__, __LINE__, #pt)
#define deallocate(pt) fbmld_deallocate((pt), __FILE__, __LINE__, #pt)

type fbmld_t
	pt      as any ptr
	bytes   as uinteger
	file    as string
	linenum as integer
	left    as fbmld_t ptr
	right   as fbmld_t ptr
end type

common shared fbmld_tree as fbmld_t ptr
common shared fbmld_mutex as any ptr
common shared fbmld_instances as integer

private sub fbmld_print(byref s as string)
	fprintf(stderr, "(FBMLD) " & s & chr(10))
end sub
 
private sub fbmld_mutexlock( )
#ifndef FBMLD_NO_MULTITHREADING
	mutexlock(fbmld_mutex)
#endif
end sub

private sub fbmld_mutexunlock( )
#ifndef FBMLD_NO_MULTITHREADING
	mutexunlock(fbmld_mutex)
#endif
end sub

 
private function new_node _
	( _
		byval pt      as any ptr, _
		byval bytes   as uinteger, _
		byref file    as string, _
		byval linenum as integer _
	) as fbmld_t ptr

	dim as fbmld_t ptr node = calloc(1, sizeof(fbmld_t))

	node->pt = pt
	node->bytes = bytes
	node->file = file
	node->linenum = linenum
	node->left = NULL
	node->right = NULL
	
	function = node

end function
 
private sub free_node _
	( _
		byval node as fbmld_t ptr _
	)

	node->file = ""
	free( node )

end sub

private function fbmld_search _
	( _
		byval root    as fbmld_t ptr ptr, _
		byval pt      as any ptr _
	) as fbmld_t ptr ptr

	dim as fbmld_t ptr ptr node = root
	dim as any ptr a = pt, b = any
	
	asm
		mov eax, dword ptr [a]
		bswap eax
		mov dword ptr [a], eax
	end asm
	
	while *node <> NULL
		b = (*node)->pt
		asm
			mov eax, dword ptr [b]
			bswap eax
			mov dword ptr [b], eax
		end asm
		if a < b then
			node = @(*node)->left
		elseif a > b then
			node = @(*node)->right
		else
			exit while
		end if
	wend
	
	function = node

end function

private sub fbmld_insert _
	( _
		byval root    as fbmld_t ptr ptr, _
		byval pt      as any ptr, _
		byval bytes   as uinteger, _
		byref file    as string, _
		byval linenum as integer _
	)

	dim as fbmld_t ptr ptr node = fbmld_search(root, pt)

	if *node = NULL then
		*node = new_node( pt, bytes, file, linenum )
	end if

end sub

private sub fbmld_swap _
	( _
		byval node1 as fbmld_t ptr ptr, _
		byval node2 as fbmld_t ptr ptr _
	)

	swap (*node1)->pt,      (*node2)->pt
	swap (*node1)->bytes,   (*node2)->bytes
	swap (*node1)->file,    (*node2)->file
	swap (*node1)->linenum, (*node2)->linenum

end sub

private sub fbmld_delete _
	( _
		byval node as fbmld_t ptr ptr _
	)

	dim as fbmld_t ptr old_node = *node
	dim as fbmld_t ptr ptr pred

	if (*node)->left = NULL then
		*node = (*node)->right
		free_node( old_node )
	elseif (*node)->right = NULL then
		*node = (*node)->left
		free_node( old_node )
	else
		pred = @(*node)->left
		while (*pred)->right <> NULL
			pred = @(*pred)->right
		wend
		fbmld_swap( node, pred )
		fbmld_delete( pred )
	end if

end sub

private sub fbmld_init _
	( _
	) constructor 101

	if fbmld_instances = 0 then
#ifndef FBMLD_NO_MULTITHREADING
		fbmld_mutex = mutexcreate()
#endif
	end if
	fbmld_instances += 1
end sub

private sub fbmld_tree_clean _
	( _
		byval node as fbmld_t ptr ptr _
	)

	if *node <> NULL then
		fbmld_tree_clean( @((*node)->left) )
		fbmld_tree_clean( @((*node)->right) )
		fbmld_print( "error: " & (*node)->bytes & " bytes allocated at " & (*node)->file & ":" & (*node)->linenum & " [&H" & hex( (*node)->pt, 8 ) & "] not deallocated" )
		(*node)->file = ""
		free( (*node)->pt )
		free( *node )
		*node = NULL
	end if
end sub

private sub fbmld_exit _
	( _
	) destructor 101

	fbmld_instances -= 1
	
	if fbmld_instances = 0 then
		
		if fbmld_tree <> NULL then
			fbmld_print("---- memory leaks ----")
			fbmld_tree_clean(@fbmld_tree)
		else
			fbmld_print("all memory deallocated")
		end if
		
#ifndef FBMLD_NO_MULTITHREADING
		if fbmld_mutex <> 0 then
			mutexdestroy(fbmld_mutex)
			fbmld_mutex = 0
		end if
#endif
	
	end if

end sub

private function fbmld_allocate(byval bytes as uinteger, byref file as string, byval linenum as integer) as any ptr
	dim ret as any ptr = any
	
	fbmld_mutexlock()
	
	if bytes = 0 then
		fbmld_print("warning: allocate(0) called at " & file & ":" & linenum & "; returning NULL")
		ret = 0
	else
		ret = malloc(bytes)
		fbmld_insert(@fbmld_tree, ret, bytes, file, linenum)
	end if
	
	fbmld_mutexunlock()
	
	return ret
end function

private function fbmld_callocate(byval bytes as uinteger, byref file as string, byval linenum as integer) as any ptr
	dim ret as any ptr = any
	
	fbmld_mutexlock()
	
	if bytes = 0 then
		fbmld_print("warning: callocate(0) called at " & file & ":" & linenum & "; returning NULL")
		ret = 0
	else
		ret = calloc(1, bytes)
		fbmld_insert(@fbmld_tree, ret, bytes, file, linenum)
	end if
	
	fbmld_mutexunlock()
	
	return ret
end function

private function fbmld_reallocate(byval pt as any ptr, byval bytes as uinteger, byref file as string, byval linenum as integer, byref varname as string) as any ptr
	dim ret as any ptr = any
	dim node as fbmld_t ptr ptr = any
	
	fbmld_mutexlock()
	
	node = fbmld_search(@fbmld_tree, pt)
	
	if pt = NULL then
		if bytes = 0 then
			fbmld_print("error: reallocate(" & varname & " [NULL] , 0) called at " & file & ":" & linenum)
			ret = NULL
		else
			ret = malloc(bytes)
			fbmld_insert(@fbmld_tree, ret, bytes, file, linenum)
		end if
	elseif *node = NULL then
		fbmld_print("error: invalid reallocate(" & varname & " [&H" & hex(pt, 8) & "] ) at " & file & ":" & linenum)
		ret = NULL
	elseif bytes = 0 then
		fbmld_print("warning: reallocate(" & varname & " [&H" & hex(pt, 8) & "] , 0) called at " & file & ":" & linenum & "; deallocating")
		free(pt)
		if *node <> NULL then fbmld_delete(node)
		ret = NULL
	else
		ret = realloc(pt, bytes)
		
		if ret = pt then
			(*node)->bytes = bytes
			(*node)->file = file
			(*node)->linenum = linenum
		else
			fbmld_delete(node)
			fbmld_insert(@fbmld_tree, ret, bytes, file, linenum)
		end if
	end if
	
	fbmld_mutexunlock()
	
	return ret
end function

private sub fbmld_deallocate(byval pt as any ptr, byref file as string, byval linenum as integer, byref varname as string)
	dim node as fbmld_t ptr ptr
	
	fbmld_mutexlock()
	
	if pt = NULL then
		fbmld_print("warning: deallocate(" & varname & " [NULL] ) at " & file & ":" & linenum)
	else
		node = fbmld_search(@fbmld_tree, pt)
		
		if *node = NULL then
			fbmld_print("error: invalid deallocate(" & varname & " [&H" & hex(pt, 8) & "] ) at " & file & ":" & linenum)
		else
			fbmld_delete(node)
			free(pt)
		end if
	end if
	
	fbmld_mutexunlock()
end sub

#endif '' __FBMLD__
Note: You must use parentheses in allocate/callocate/reallocate/deallocate calls when using FBMLD. For example, this won't work:

Code: Select all

deallocate p
Instead, write this:

Code: Select all

deallocate(p)
Web page: http://drv.nu/freebasic/fbmld/

Version 0.6 and above require a recent fbc version 0.17 CVS build. Use version 0.5 (available on the web page above) if you must use an older version of FreeBASIC.
Last edited by DrV on Jan 18, 2007 17:26, edited 9 times in total.
rdc
Posts: 1741
Joined: May 27, 2005 17:22
Location: Texas, USA
Contact:

Post by rdc »

Nifty.
JohnB
Posts: 236
Joined: Jul 22, 2005 3:53
Location: Minnesota Arizona

Post by JohnB »

Thanks for posting the code. I have some scrolling code that seem cause a lockup after it runs for the 4th or 5th time. Hope your code helps me find the problem.

Thanks again.

JohnB
Stormy
Posts: 198
Joined: May 28, 2005 17:57
Location: Germany
Contact:

Post by Stormy »

I cannot use your tool, because some compiling error stops me.

Code: Select all

engine.bas(2760) : error 42: Variable not declared, found: 'DeAllocate'

DeAllocate Battlefield.HPmeter
HPmeter is a UBYTE PTR and refers to the hitpoint-meter image.

Edit: My fault ! I had to write the correspond pointer in brackets !
Stormy
Posts: 198
Joined: May 28, 2005 17:57
Location: Germany
Contact:

Post by Stormy »

I found a very very very very confusing bug, that took me several hours to solve it !

Check this source out... Message will be:

(FBMLD) Tried to deallocate unallocated memory at &H8087A88 on line XX of memleak2.bas!

Code: Select all

DECLARE FUNCTION Dummy () AS UBYTE PTR

FUNCTION Dummy () AS UBYTE PTR
DIM buffer AS UBYTE PTR
buffer = Allocate((50*50+1)*4)
BLOAD "50x50.bmp",buffer
RETURN buffer
END FUNCTION

#define FBMLD_NO_MULTITHREADING 
#include "fbmld.bi"

SCREENRES 640,480,16
DIM Buffer AS UBYTE PTR
Buffer = Dummy()
PUT (100,100), Buffer, PSET
DeAllocate (Buffer)
SLEEP
END
Then take a look at this one... no error message!!

Code: Select all

DECLARE FUNCTION Dummy () AS UBYTE PTR

#define FBMLD_NO_MULTITHREADING 
#include "fbmld.bi"

SCREENRES 640,480,16
DIM Buffer AS UBYTE PTR
Buffer = Dummy()
PUT (100,100), Buffer, PSET
DeAllocate (Buffer)
SLEEP
END

FUNCTION Dummy () AS UBYTE PTR
DIM buffer AS UBYTE PTR
buffer = Allocate((50*50+1)*4)
BLOAD "50x50.bmp",buffer
RETURN buffer
END FUNCTION
No error message, just because I moved the function dummy() at the end of the source.

Please fix this soon !
1000101
Posts: 2556
Joined: Jun 13, 2005 23:14
Location: SK, Canada

Post by 1000101 »

Stormy wrote:I found a very very very very confusing bug, that took me several hours to solve it !

Check this source out... Message will be:

...


No error message, just because I moved the function dummy() at the end of the source.

Please fix this soon !
The problem isn't with FBMLD but with your code. The function doesn't have to be at the end, the include must be *BEFORE* all compilable code or code which would depend on it. Includes *ALWAYS* go at the begining of the program source, *NEVER* in the middle.
cha0s
Site Admin
Posts: 5319
Joined: May 27, 2005 6:42
Location: USA
Contact:

Post by cha0s »

yeah, the error comes because in your first example, you're still using the fb intrinsic "allocate", when fbmld is included it "hijacks" the __allocate functions, and sets up lists, so in the first example, you used fb's allocate, then fbmld hijacks it, then you try to deallocate (using fbmld's hijacked deallocate and mem management lists), and fbmld reports that you have not allocated this memory (using its hijacked allocate)

make sense? ;p
Stormy
Posts: 198
Joined: May 28, 2005 17:57
Location: Germany
Contact:

Post by Stormy »

true, I'm convinced ^^
Dr_D
Posts: 2451
Joined: May 27, 2005 4:59
Contact:

Post by Dr_D »

I thought I remebered seeing something like this, but I couldn't remember the name. It's very helpful. Thanks, DrV! :D
cha0s
Site Admin
Posts: 5319
Joined: May 27, 2005 6:42
Location: USA
Contact:

Post by cha0s »

hey i was thinking about this when i was perusing fb compiler src, but i forgot to mention... seems you can do the "multithreading" switch intrinsically now.. __FB_MT__ i think?
relsoft
Posts: 1767
Joined: May 27, 2005 10:34
Location: Philippines
Contact:

Post by relsoft »

neat!!!
Nexinarus
Posts: 146
Joined: May 28, 2005 6:08
Location: Everywhere
Contact:

Post by Nexinarus »

Nice ;). I saw something like this in a tutorial on gamedev.net or flipcode or something, except it was for vc++ 6 and the code was so nasty.

I think this should be included everytime you compile a freebasic program to debug mode, asin put in the FBC distribution itself. Or would that be stupid?
1000101
Posts: 2556
Joined: Jun 13, 2005 23:14
Location: SK, Canada

Post by 1000101 »

The problem with that Nex, is that DrV's leak detector needs more variables for the allocation. It wants the file, function and line number so it can report where (in code) the memory is allocated and subsequentally not being released. Of course, with debug building, the compiler could add those into the call itself without help and use the leak detector functions directly. So...nevermind then :P
Nexinarus
Posts: 146
Joined: May 28, 2005 6:08
Location: Everywhere
Contact:

Post by Nexinarus »

Yeh well that stuff is easily added, for example in c++ they are obtained by pre processor variables I think.
DrV
Site Admin
Posts: 2116
Joined: May 27, 2005 18:39
Location: Midwestern USA
Contact:

Post by DrV »

cha0s wrote:hey i was thinking about this when i was perusing fb compiler src, but i forgot to mention... seems you can do the "multithreading" switch intrinsically now.. __FB_MT__ i think?
That'll catch the -mt compiler option, but it won't work if the user is depending on the compiler automatically picking the mt runtime because they use threading functions. Anyway, the default is safe, but if people want to shoot themselves in the feet... :)
Post Reply