So, NJAMD (Not Just Another Malloc Debugger) is just that. It provides memory
debugging capabilities simalair to purify.

NJAMD replaces the standard system memory allocator with it's own version that
performs various checks on your memory usage.

Basically it has three main features:
1. It protects against dynamic buffer overflows.
Overflows are common mistake made by programmers whereby a buggy program
overwrites past the end of a requested region of memory. 
An example would be:
   char *buf = malloc(5); // Requests 5 bytes of memory and use the variable 
                          // buf to reference it.
   strcpy(buf, "12345"); // Copy "12345" into the memory at variable buf.
                         // The string "12345" has 6 characters. In C and C++
                         // strings always end with an invisible "Null"
                         // character, which takes up an extra byte

Under normal operation, this type of error can be extremely difficult to track
down. The default C library memory allocator malloc devides up system memory and
returns the requested length of memory to the user. However, internally the
memory allocator stores bookkeeping information about allocated memory before 
and after the buffer itself. So even this one byte error can cause the memory
allocator to become confused much later on during successive memory requests.
This can casue all kinds of unpredictable behavior. All because of one byte!

Of course, barring not making the mistake in the first place, the most
desirable outcome is to have an error generated immediately, so the programmer
can discern what is wrong, and can quickly fix it.

The way this is accomilished is by telling the operating system to place a 
section of "protected" memory surrounding each allocation request. However, it
is not that simple. Memory can only be protected in multiples called "pages",
which are typically 4096 bytes long. So consequently we must properly allign
the user's buffer so that it ends on a page boundry. 
 
Start of memory obtained 
from Operating System       start of buf        Page Boundry
\/                               \/                    \/             
|~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|~~~~~~~~~~~~~~~/ /~~~||~~~~~~~~~~~~~~~~~~|
| offset to align to page boundry| User's memory \ \   || Protected Memory |
|________________________________|_______________/ /___||__________________|

Now, whenever the user overwrites his memory, even by one byte, he writes to
the protected memory, and the operating systems generates what's called a
"Segmentation Fault", and kills his process. Using the front end of our
utility, (which uses the GNU debugger 'gdb'), the user can discern to the line
where in his program this error occured, and fix the error in a matter of
seconds, instead of scratching his head wondering why his program appears to
work correctly for hours until finnaly the memory allocator corrupts itself 
due to its confusion. The page of protected memory also forbids reads, this
stopping the user's program from reading invalid data outside of it's
allocated buffer.

2. In a simalar way, our program protects against underflows.
A buffer undeflow is the exact opposite condition. It's where a user
reads or writes BEFORE his section. A sample piece of code might look
something like:
  int i;
  ... 
  i = -1;
  ...
  buf[i] = 'J';

This assigns the character J to an memory address 1 byte below the user's
buffer. This can also confuse the memory allocator down the line. 

The way we handle this is to place a section of protected memory before the
user's region. This case is much simpler:

Start of memory
from OS                                                   
(Page boundry)   Page boundry      End of User's memory    Page boundry
\/                  \/              \/                     \/
|~~~~~~~~~~~~~~~~~~~||~~~~~~~~~~~~~~~|~~~~~~~~~~~~~~~~~~~~~|
| Protected memory  || User's memory | Extra padding space |
|___________________||_______________|_____________________|


3. A third common mistake made by programmers is to accidentally use memory
after it has been returned to the memory allocator by free.
Ex:
   free(buf)            // Returns buf to allocator
   strcpy(buf, "1234"); // copies 5 bytes to buf, but buf doesn't exist!

Normally, this would confuse the allocator much like overflows and underflows
do. The allocator expects the memory at buf to be available for future
allocations, and writing to it may cause you to overwrite your own memory used
elsewhere!

This solution is very simple, we tell the operating system to put a section of
protected memory spanning the entire region of the old memory.

Ex: 
|~~~~~~~~~~~~~~~~~~~||~~~~~~~~~~~~~~~|~~~~~~~~~~~~~~~~~~~~~|
| Protected memory  || User's memory | Extra padding space |
|___________________||_______________|_____________________|

Becomes:

|~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|
|                   Protected memory                       |
|__________________________________________________________|

when the user issues free(buf). 
That way, any accesses to the memory will immediately result in a segmentation
fault. NJAMD does not reuse this memory. However, the memory is not wasted!
Modern operating systems do not actually allocate memory at an address until 
it is accessed by a program. Until this access, the operating system merely
keeps track that there SHOULD be real memory there. True, at one point the 
memory at this address WAS accessed, but we told the operating system to
discard that, and replace it with protected memory instead. Memory that has
never (and will never) be accessed. Thus there is no waste!