(FBMLD) FreeBASIC Memory Leak Detector

Source-code only - please, don't post questions here.
DrV
Site Admin
Posts: 2116
Joined: May 27, 2005 18:39
Location: Midwestern USA
Contact:

(FBMLD) FreeBASIC Memory Leak Detector

Postby DrV » Mar 30, 2006 17:51

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: 1713
Joined: May 27, 2005 17:22
Location: Texas, USA
Contact:

Postby rdc » Mar 30, 2006 21:00

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

Postby JohnB » Mar 30, 2006 23:53

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:

Postby Stormy » Apr 13, 2006 10:18

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:

Postby Stormy » Apr 13, 2006 14:03

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

Postby 1000101 » Apr 13, 2006 18:30

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: 5317
Joined: May 27, 2005 6:42
Location: Illinois
Contact:

Postby cha0s » Apr 13, 2006 22:27

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:

Postby Stormy » Apr 13, 2006 23:07

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

Postby Dr_D » Jul 20, 2006 0:48

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: 5317
Joined: May 27, 2005 6:42
Location: Illinois
Contact:

Postby cha0s » Jul 20, 2006 1:04

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:

Postby relsoft » Jul 20, 2006 1:37

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

Postby Nexinarus » Jul 20, 2006 1:51

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

Postby 1000101 » Jul 20, 2006 6:46

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:

Postby Nexinarus » Jul 20, 2006 11:16

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:

Postby DrV » Jul 20, 2006 12:41

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... :)

Return to “Tips and Tricks”

Who is online

Users browsing this forum: No registered users and 1 guest