Appendix BSolving Memory Overwrite Problems

Overwriting of memory is the most common cause of bizarre behavior within Vermont Views programs. Overwriting of memory is often caused by writing to uninitialized pointer spaces or overstepping the bounds of arrays. There are no easy answers for memory overwrite problems.

The following outlines the steps you should take to find the source of memory overwrite problems.

Step 1: Run your Vermont Views program linked with the development libraries and use the tools provided by the VCS error reporting system.

See Chapter 50, "Debugging and Error Handling" for more information about using and interpreting the information given by the VCS error reporting system.

Step 2: Check the limits on all the arrays.

Remember that indexing goes from 0 to ARRAY_SIZE - 1.

Make sure you allocated a space for the terminal null in any array of characters allocated for a string variable.

Step 3: Check for misuse of character pointers that do not have memory allocated to hold the contents of a string.

Defining a UCHAR * does not allocate the memory at the pointer location. An example of this type of error is:

UCHAR *p;        /*user assumes p points to valid memory    */

strcpy(p, "This corrupts memory");

Step 4: Check for uninitialized pointers.

You will have problems when writing to, calling, or passing uninitialized pointers.

Writing to uninitialized pointers can cause many kinds of problems, including data corruption and unpredictable program behavior. Writing to uninitialized pointers is often the cause of operating system problems in models with long pointers (large address space). Low memory will be overwritten if writing is done to the address of an uninitialized global (0:0).

The following code fragment gives an example of writing to an uninitialized pointer:

UCHAR *my_string;

strcpy(my_string, "This is a memory overwrite");

A more subtle way of using uninitialized pointers is illustrated in the following code fragment:

int my_func()

{

    WINDOWPTR wnp;

    DFORMPTR fmp;

    DFIELDPTR fldp;



    fldp = fld_def(......., fmp);



    if (! (fmp = fm_def(.....)))

        goto ERROR;



    wnp = wn_def(....);



ERROR:

    wn_free(wnp);



    return(1);

}

There are several errors in this fragment. First, the call to fld_def() uses fmp, which is not initialized until after the call to fm_def() several steps later. Also, if an error occurred in the fm_def() call and the program jumped to the ERROR label, the window pointer wnp is freed by calling wn_free(), even though it has never been initialized.

Problems can also arise when you fail to check the return value from functions that can generate run-time errors. As an example, the following code does not check for run-time errors:

int usr_func(fmp)

FORMPTR fmp;

{

    DLIBPTR libp;

    DFORMPTR my_formp;

    MY_DATA data;



    libp = dl_open("my_lib.vvd");

    my_formp = dl_fmget("my_data", &data, NULLP, NULLP,

            libp);

    fm_proc(0, my_formp);

    fm_free(my_formp);

    dl_close(libp);

    return(1);

}

No error checking is done in this example. Both dl_open() and dl_fmget() can fail due to run-time errors. Since no error checking is done, the program would continue to run, using NULLP for the value of libp and/or my_formp. This could result in processing a non-existent form, and eventually attempting to free memory at location 0:0, where no memory was allocated. All these errors are memory overwrite errors which could have serious implications. These errors would be caught by the development library, but not by the production library. Properly structured code would check for run-time errors, and would appear as follows:

int usr_func(fmp)

FORMPTR fmp;

{

    DLIBPTR libp;

    DFORMPTR my_formp;

    MY_DATA data;



    if (libp = dl_open("my_lib.vvd"))

    {

        if (my_formp = dl_fmget("my_data", &data, NULLP,

            NULLP, libp))

        {

             fm_proc(0, my_formp);

             fm_free(my_formp);

        }

        dl_close(libp);

    }

    return(1);

}

If an uninitialized function pointer is called, the program will generally crash.

You should be careful with functions that have pointers as arguments. Check your function calls for parameter values of zero when the parameter should be a NULLP or NULLFP. This causes problems with any memory model except the small memory model.

Try setting your compiler to the highest warning level. Some compilers will detect uninitialized pointers at the highest warning level. Check your compiler manual for details.

Step 5: Check for poorly specified data variables.

If processing dies or becomes confused after processing a form, the likely cause is a poorly specified data variable. Check if any of the following are true:

Step 6: Attempt to find a data location (variable, structure, etc.) that is consistently corrupted.

Use a debugging tool or a print statement to check the value of variables at various points in your program until you identify the code that is causing the corruption.

Step 7: Break your program into smaller pieces.

If all else fails, you must begin to break your program into smaller pieces or strip away suspect sections of code until the problem is isolated.


Home Contents Previous Next