[Python-Dev] Debug entry points for PyMalloc

Michael Hudson mwh@python.net
21 Mar 2002 13:03:09 +0000


Tim Peters <tim.one@comcast.net> writes:

> The thing I've dreaded most about switching to pymalloc is losing the
> invaluable memory-corruption clues supplied by the Microsoft debug-build
> malloc.  On more than one occasion, they've found wild stores, out-of-bounds
> reads, reads of uninitialized memory, and reads of free()ed memory in
> Python.  It does this by spraying special bytes all over malloc'ed memory at
> various times, then checking the bytes for sanity at free() and realloc()
> times.

[...]

> Sketch of Debug Mode for PyMalloc
> 
> + Three new entry points in obmalloc.c (note:  stop #include'ing this;
>   hiding code in include files sucks, and naming an include file .c
>   compounds the confusion):

+1

> DL_IMPORT(void *) _PyMalloc_DebugMalloc(size_t nbytes);
> DL_IMPORT(void *) _PyMalloc_DebugRealloc(void *p, size_t nbytes);
> DL_IMPORT(void) _PyMalloc_DebugFree(void *p);
> 
> + When WITH_PYMALLOC and PYMALLOC_DEBUG are #define'd, these are
>   mapped to in the obvious way from _PyMalloc_{MALLOC, REALLOC, FREE}:
> 
> #ifdef WITH_PYMALLOC
> DL_IMPORT(void *) _PyMalloc_Malloc(size_t nbytes);
> DL_IMPORT(void *) _PyMalloc_Realloc(void *p, size_t nbytes);
> DL_IMPORT(void) _PyMalloc_Free(void *p);
> 
> DL_IMPORT(void *) _PyMalloc_DebugMalloc(size_t nbytes);
> DL_IMPORT(void *) _PyMalloc_DebugRealloc(void *p, size_t nbytes);
> DL_IMPORT(void) _PyMalloc_DebugFree(void *p);
> 
> #ifdef PYMALLOC_DEBUG
> #define _PyMalloc_MALLOC _PyMalloc_DebugMalloc
> #define _PyMalloc_REALLOC _PyMalloc_DebugRealloc
> #define _PyMalloc_FREE _PyMalloc_DebugFree
> 
> #else   /* WITH_PYMALLOC && !PYMALLOC_DEBUG */
> #define _PyMalloc_MALLOC _PyMalloc_Malloc
> #define _PyMalloc_REALLOC _PyMalloc_Realloc
> #define _PyMalloc_FREE _PyMalloc_Free
> 
> #endif  /* PYMALLOC_DEBUG */
> 
> #else   /* !WITH_PYMALLOC */
> #define _PyMalloc_MALLOC PyMem_MALLOC
> #define _PyMalloc_REALLOC PyMem_REALLOC
> #define _PyMalloc_FREE PyMem_FREE
> #endif  /* WITH_PYMALLOC */

Presuambly it would be possible to do this wrapped around malloc() &
free() too?  No real point, I guess.

> + A debug build implies PYMALLOC_DEBUG, but PYMALLOC_DEBUG can
>   be forced in a release build.
> 
> + No changes to the guts of _PyMalloc_{Malloc, Realloc, Free}.  Keep
>   them as lean and as clear of #ifdef obscurity as they are now.

+1

> + Define three special bit patterns.  In hex, they all end with B
>   (for deBug <wink>), and begin with a vaguely mnemonic letter.
>   Strings of these are unlikely to be legit memory addresses, ints,
>   7-bit ASCII, or floats:

[snip good stuff]

> + The Debug free first uses the address to find the number of bytes
>   originally asked for, then checks the 8 bytes on each end for
>   sanity (in particular, that the PYMALLOC_FORBIDDENBYTEs are still
>   intact).
>   XXX Make this checking a distinct entry point.

Yes.  Particularly if you can call it from gdb.

>   XXX In case an error is found, print informative stuff, but then what?
>   XXX Die or keep going?  Fatal error is probably best.
>   Then fills the original N bytes with PYMALLOC_DEADBYTE.  This is to
>   catch references to free()ed memory.  The forbidden bytes are left
>   intact.
>   Then calls _PyMalloc_Free.

Is it worth having an option where you *don't* call _Free?  Obviously,
this would chew memory like no tomorrow, but it might just catch more
errors.

> + The Debug realloc first calls _PyMalloc_DebugMalloc with the new
>   request size.
>   Then copies over the original bytes.
>   The calls _PyMalloc_DebugFree on the original bytes.
>   XXX This could, and probably should, be optimized to avoid copying
>   XXX every time.

Yes.

What are you waiting for? <wink>

Cheers,
M.

-- 
  If comp.lang.lisp *is* what vendors are relying on to make or 
  break Lisp sales, that's more likely the problem than is the 
  effect of any one of us on such a flimsy marketing strategy...
                                      -- Kent M Pitman, comp.lang.lisp