Files
sics/fortify.doc

410 lines
18 KiB
Plaintext

Fortify - fortified memory allocation shell for C and C++
---------------------------------------------------------
written by Simon P. Bullen
Cybergraphic
Release 1.0, 2/2/1995
This software is not public domain. All material in
this archive is © Copyright 1995 Simon P. Bullen. The
software is freely distrubtable, with the condition that no
more than a nominal fee is charged for media. Everything in
this distrubution must be kept together, in original,
unmodified form.
The files may be modified for your own personal use, but
modified files may not be distributed.
The material is provided "as is" without warranty of any
kind. The author accepts no responsibilty for damage caused
by this software.
email: sbullen@ozemail.com.au
snail: Simon P. Bullen
PO BOX 12138
A'Beckett St
Melbourne 3000
Australia
CONTENTS
--------
Your archive should have the following files:
file_id.diz
fortify.c
fortify.doc
fortify.h
read.me
test.c
ufortify.h
ufortify.hpp
zfortify.cpp
zfortify.hpp
ztest.cpp
OVERVIEW
--------
Fortify is a descendant of a library I wrote way back in 1990 called
SafeMem. It is a (fortified) shell for memory allocations. It works with
the malloc/free family of functions, as well as new/delete in C++. It can
be adapted to most memory management functions; the original version of
SafeMem worked only with the Amiga's AllocMem/FreeMem. I haven't been
writing much Amiga specific software lately, so the Amiga version of
Fortify is way out of date, hence it's absense from this archive.
Fortify is designed to detect bad things happening, or at the very
least encourage intermittent problems to occur all the time. It is capable
of detecting memory leaks, writes beyond and before memory blocks, and
breaks software that relies on the state of uninitialized memory, and
software that uses memory after it's been freed.
It works by allocating extra space on each block. This includes a
private header (which is used to keep track of Fortify's list of allocated
memory), and two "fortification" zones or sentinals (which are used to
detect writing outside the bound of the user's memory).
Fortify VERSUS ZFortify
-----------------------
Fortify provides fortification for malloc/realloc/free, and ZFortify
provides fortifications for new/delete. It is possible to use both
versions in the one program, if you are mixing C and C++. Just make sure
(as you already should be) that you never free() memory you new'd, and
other such illegal mismatching.
(Why _Z_ fortify? It's a long story...)
Fortify INSTALLATION (C)
------------------------
To use Fortify, each source file will need to #include "fortify.h". To
enable Fortify, define the symbol FORTIFY. If FORTIFY is not defined, it
will compile away to nothing. If you do not have stdout available, you may
wish to set an alternate output function. See Fortify_SetOutputFunc(),
below.
You will also need to link in fortify.o
ZFortify INSTALLATION (C++)
---------------------------
The minimum you need to do for ZFortify is to define the symbol
ZFORTIFY, and link zfortify.o. Each source file should also include
"zfortify.hpp", but this isn't strictly necessary. If a file doesn't
#include "Fortify.hpp", then it's allocations will still be fortified,
however you will not have any source-code details in any of the output
messages (this will be the case for all libraries, etc, unless you have the
source for the library and can recompile it with Fortify).
If you do not have stdout available, you may wish to set an alternate
output function, or turn on the AUTOMATIC_LOGFILE. See
ZFortify_SetOutputFunc() and AUTOMATIC_LOGFILE, below.
COMPILE TIME CUSTOMIZATIONS
---------------------------
The files "ufortify.h" and "ufortify.hpp" contain a number of #defines
that you can use to customize Fortify's behavior.
#define ZFORTIFY_PROVIDE_ARRAY_NEW
Some C++ compilers have a separate operator for newing arrays. If your
compiler does, you will need to define this symbol. If you are unsure,
dont worry about it too much, your program won't compile or link without
the correct setting. GCC 2.6.3 and Borland C++ 4.5 both need this symbol.
Microsoft C++ 1.5 and SAS 6.5 C++ both dont.
#define FORTIFY_STORAGE
#define ZFORTIFY_STORAGE
You can use this to apply a storage type to all of Fortify's exportable
functions. If you are putting Fortify in an export library for example,
you may need to put __export here, or some such rubbish.
#define FORTIFY_BEFORE_SIZE 32
#define FORTIFY_BEFORE_VALUE 0xA3
#define FORTIFY_AFTER_SIZE 32
#define FORTIFY_AFTER_VALUE 0xA5
#define ZFORTIFY_BEFORE_SIZE 32
#define ZFORTIFY_BEFORE_VALUE 0xA3
#define ZFORTIFY_AFTER_SIZE 32
#define ZFORTIFY_AFTER_VALUE 0xA5
These values define how much "fortification" is placed around each
memory block you allocate. Fortify will place _BEFORE_SIZE bytes worth of
memory right before your allocation block, and _AFTER_SIZE bytes worth
after your allocation block, and these will be initialized to _BEFORE_VALUE
and _AFTER_VALUE respectively. If your program then accidentally writes
too far beyond the end of the block, for example, Fortify will be able to
detect this (so long as you didn't happen to write in _AFTER_VALUE!).
If you don't want these fortifications to be allocated, specify a
_SIZE of 0. Note that the _VALUE parameters are 8 bits.
#define FILL_ON_MALLOC
#define FILL_ON_MALLOC_VALUE 0xA7
#define FILL_ON_NEW
#define FILL_ON_NEW_VALUE 0xA7
Programs often rely on uninitialized memory being certain values
(usually 0). If you define FILL_ON_NEW, all memory that you new will be
initialized to FILL_ON_NEW_VALUE, which you should define to be some horrid
value (definately NOT 0). This will encourage all code that relies on
uninitialized memory to behave rather differently when Fortify is running.
#define FILL_ON_FREE
#define FILL_ON_FREE_VALUE 0xA9
#define FILL_ON_DELETE
#define FILL_ON_DELETE_VALUE 0xA9
Programmers often try to use memory after they've freed it, which can
sometimes work (so long as noboby else has modified the memory before you
look at it), but is incredibly dangerous and definately bad practice. If
FILL_ON_DELETE is defined, all memory you free will be stomped out with
FILL_ON_DELETE_VALUE, which ensures that any attempt to read freed memory
will give incorrect results.
#define CHECK_ALL_MEMORY_ON_MALLOC
#define CHECK_ALL_MEMORY_ON_FREE
#define CHECK_ALL_MEMORY_ON_NEW
#define CHECK_ALL_MEMORY_ON_DELETE
CHECK_ALL_MEMORY_ON... means that for every single memory allocation
or deallocation, every single block of memory will be checked. This can
considerably slow down programs if you have a large number of blocks
allocated. You would normally only need to turn this on if you are trying
to pinpoint where a corruption was occurring.
A block of memory is always checked when it is freed, so if
CHECK_ALL... isn't turned on, corruptions will still be detected
eventually.
You can also force Fortify to check all memory with a call to
Fortify_CheckAllMemory(). If you have a memory corruption you can't find,
sprinkling these through the suspect code will help narrow it down.
#define PARANOID_FREE
#define PARANOID_DELETE
PARANOID_... - This means that zFortify traverses the memory list to
ensure the memory you are about to free was really allocated by it. You
probably only need this in extreme circumstances. Not having this defined
will still trap attempts to free memory that wasn't allocated, unless
someone is deliberately trying to fool zFortify.
Paranoid mode adds considerable overhead to freeing memory, especially
if you are freeing things in the same order you allocated them (Paranoid
mode is most efficient if you are freeing things in the reverse order they
were allocated).
#define WARN_ON_MALLOC_FAIL
#define WARN_ON_ZERO_MALLOC
#define WARN_ON_FALSE_FAIL
#define WARN_ON_UNSIGNED_LONG_OVERFLOW
#define WARN_ON_NEW_FAIL
#define WARN_ON_ZERO_NEW
These defines enable the output of warning that aren't strictly errors,
but can be useful to determine what lead to a program crashing.
WARN_ON_NEW_FAIL causes a debug to be issued whenever new fails.
WARN_ON_ZERO_NEW causes a debug to be issued whenever a new of a zero
byte object is attempted. This is fairly unlikely in C++, and is much more
likely when using malloc().
WARN_ON_FALSE_FAIL causes a debug to be issued when a new is "false"
failed. ZSee Fortify_SetNewFailRate() for more information.
WARN_ON_UNSIGNED_LONG_OVERFLOW causes Fortify to check for breaking the
32 bit limit. This was more of a problem in 16-bit applications where
breaking the 16 bit limit was much more likely. The problem is that
Fortify adds a small amount of overhead to a memory block; so in a 16-bit
size_t environment, if you tried to allocate 64K, Fortify would make that
block bigger than 64K and your allocation would fail due to the presence of
Fortify. With size_t being 32 bits for all environments worth programming
in, this problem is extremely unlikely (Unless you plan to allocate 4
gigabytes).
#define AUTOMATIC_LOG_FILE
#define LOG_FILENAME "fortify.log"
#define FIRST_ERROR_FUNCTION
If AUTOMATIC_LOG_FILE is defined (C++ version /ZFortify only), then
Fortify will be automatically started for you, Fortify messages sent to the
named log file, and a list of unfreed memory dumped on termination (where
the log file will be automatically closed for you. If no Fortify was
output, the log file will not be altered. There are timestamps in the log
file to ensure you're reading the correct messages.
FIRST_ERROR_FUNCTION will be called upon generation of the first
Fortify message, so that the user can tell a Fortify report has been
generated. Otherwise, Fortify would quietly write all this useful stuff
out to the log file, and no-one would know to look there!
#define FORTIFY_LOCK()
#define FORTIFY_UNLOCK()
#define ZFORTIFY_LOCK()
#define ZFORTIFY_UNLOCK()
In a multi-threaded environment, we need to arbitrate access to the
foritfy memory list. This is what ZFORTIFY_LOCK() and ZFORTIFY_UNLOCK()
are used for. The calls to ZFORTIFY_LOCK() and ZFORTIFY_UNLOCK() must
nest. If no two threads/tasks/processes will be using the same Fortify at
the same time, then ZFORTIFY_LOCK() and ZFORTIFY_UNLOCK() can safely be
#defined away to nothing.
RUN TIME CONTROL
----------------
Fortify can also be controlled at run time with a few special
functions, which compile away to nothing if FORTIFY isn't defined, and do
nothing if Fortify has been disabled with Fortify_Disable(). These
functions all apply to ZFortify as well (The ZFortify versions have a
ZFortify_ prefix, of course).
Fortify_Disable() - This function provides a mechanism to disable
Fortify without recompiling all the sourcecode. It can only be called,
though, when there is no memory on the Fortify list. (Ideally, at the
start of the program before any memory has been allocated). If you call
this function when there IS memory on the Fortify list, it will issue an
error, and Fortify will not be disabled.
Fortify_SetOutputFunc(Fortify_OutputFuncPtr Output) - Sets the function
used to output all error and diagnostic messages by Fortify. The output
function takes a single (const char *) argument, and must be able to handle
newlines. The function returns the old pointer.
The default output func is a printf() to stdout. Unless you are using
AUTOMATIC_LOG_FILE, where the default is to output to the log file.
Fortify_SetNewFailRate(int Percent) - Fortify will make a new attempt
"fail" this "Percent" of the time, even if the memory IS available. Useful
to "stress-test" an application. Returns the old value. The fail rate
defaults to 0.
DIAGNOSTIC FUNCTIONS
--------------------
Fortify also provides some additional diagnostic functions which can be
used to track down memory corruption and memory leaks. If Fortify is
disabled, these functions do nothing. If calling these functions directly
from a debugger, remember to add the "char *file" and "unsigned long line"
paramters to each of the calls. (The ZFortify versions have a
ZFortify_ prefix, of course).
Fortify_CheckPointer(void *uptr) - Returns true if the uptr points to a
valid piece of Fortify'd memory. The memory must be on Fortify's list, and
it's sentinals must be in tact. If anything is wrong, an error message is
issued (Note - if Fortify is disabled, this function always returns true).
Fortify_CheckAllMemory() - Checks the sentinals of all malloc'd memory.
Returns the number of blocks that failed. (If Fortify is disabled, this
function always returns 0).
Fortify_OutputAllMemory() - Outputs the entire list of currently
allocated memory. For each block is output it's Address, Size, the
SourceFile and Line that allocated it, and the fortify scope from within it
was allocated. If there is no memory on the list, this function outputs
nothing. It returns the number of blocks on the list, unless Fortify has
been disabled, in which case it always returns 0.
Fortify_DumpAllMemory(scope) - Just like Fortify_OutputAllMemory,
except all memory inside the given scope is output, and a hex dump of each
block is included in the output.
Fortify_EnterScope() - enters a level of fortify scope. Returns the
new scope level.
Fortify_LeaveScope() - leaves a level of fortify scope, it also prints
a dump of all memory allocated within the scope being left. This can be
very useful in tracking down memory leaks in a part of a program. If you
place a EnterScope/LeaveScope pair around a set of functions that should
have no memory allocated when it's done, Fortify will let you know if this
isn't the case.
PROBLEMS WITH THE new AND delete MACROS
---------------------------------------
Due to limitations of the preprocessor, getting caller sourcecode
information isn't as easy as it is for malloc() and free(). The macro for
"new" which adds this information onto the new call causes syntax errors if
you try to declare a custom new operator. The actual Fortifying works
fine, it's just the macro expansion which causes problems.
If this happens, you will need to place #undef's and #define's around
the offending code (sorry). Alternatively, you can not #include
"zfortify.hpp" for the offending file. But remember that none of the
allocation done in that file will have sourcecode information.
eg.
#undef new
void *X::operator new(size_t) { return malloc(size_t); }
#define new Fortify_New
Due to a limitation with delete, Fortify has limited information about
where delete is being called called from, and so the the line and source
information will often say "delete.0". If a delete is occuring from within
another delete, Fortify will always endeavour to report the highest level
delete as the caller.
It should be possible to replace the "new.0" and "delete.0" with the
return address of the function, which would be useful when in a debugger,
but this would be highly architecture dependant, so I leave that as an
exercise for the students :-).
WHEN TO USE FORTIFY
-------------------
The simple answer to this is "All The Time". You should never be
without Fortify when you're actually developing software. It will detect
your bugs _as_you_write_them_, which makes them a lot easier to find. One
programmer who recently started using Fortify when he had a very strange
memory problem, spent at least 3 or 4 days tracking down _other_ memory
corruption bugs that he wasn't even aware of before the program would stay
up long enough to get to his original problem. If he'd been using Fortify
from the beginning, this wouldn't have been a problem.
Leave fortify enabled until the final test and release of your
software. You probably won't want some of the slower options, such as
CHECK_ALL_MEMORY_ON_FREE, and PARANOID_FREE. With the exception of those
options, Fortify doesn't have a great deal of overhead. If posing a great
problem, this overhead can be greatly reduced by cutting down on the size
of the fortifications, and turning off the pre/post fills, but each thing
you turn off gives fortify less information to work with in tracking your
bugs.