CSE 532S Testing and Debugging Studio

Please complete the required studio exercises listed below, along with any of the (optional) enrichment exercises that interest you.

As you work through the exercises, please record your answers in a file, and upon completion please e-mail your answers to the cse532@seas.wustl.edu course e-mail account with Testing and Debugging Studio in the subject line.

Please make sure that the name of each person who worked on these exercises is listed in the first answer, and that you number your answers so they are easy for us to match up with the appropriate exercise.


    Required Exercises

  1. As the answer to the first exercise, list the names of the people who worked together on this studio.

  2. Open up Visual Studio 2013, make sure your settings are for C++, and create a new project for this studio (for example named something like testing_and_debugging).

    Add a separate source and header file (for a thread event recorder class and its helper code) to your project, and in the header file declare an enumerated type for the different kinds of events that may be of interest in debugging concurrent code (feel free to add any other kinds of events that you'd like to record):

    • when a function is just about to call another function
    • when a function has just been called
    • when a function is just about to return
    • when a function call has just returned
    • when a new object is about to be allocated dynamically on the heap
    • when a new object has just been allocated dynamically on the heap
    • when a dynamically allocated object is about to be deallocated
    • when a dynamically allocated object has just been deallocated
    • when a mutex is about to be acquired
    • when a mutex has just been acquired
    • when a mutex is about to be released
    • when a mutex has just been released
    • when a thread is just about to wait on a condition variable
    • when a thread wakes up from waiting on a condition variable
    • when a thread notifies one waiting thread
    • when a thread notifies all waiting threads
    • an unknown event has occurred (useful for default construction, too)

    In those files also declare and define a stand-alone (i.e., not a member of any class or struct) overloaded insertion operator (operator<<) that takes a reference to an ostream and a value of that enumerated type, and inserts a short string corresponding to the value that was passed (e.g., "calling", "called", "returning", "returned from", etc.) into the ostream and then returns a reference to the ostream.

    In the main C++ source code file for the project (which should be named something like testing_and_debugging.cpp) please modify the main function signature so that it looks like the standard (i.e., portable between Windows and Linux) main function entry point for C++: int main (int, char * [])

    In your main function, use the enumerated type and insertion operator to print out messages marking when the main function has just been called and when it is just about to return. Build and run your program, and as the answer to this exercise, please show your code and the output your program produced.

  3. Declare and define a struct that can record additional information about each event including (again feel free to add other fields you may find useful):
    • the id of the thread that is recording the event
    • a (void *) pointer to the object on which the currently executing function was called (which should be non-zero for class/struct methods/operators, or zero for stand-alone functions/operators)
    • a (void *) pointer to the currently executing function
    • a (void *) pointer to an additional object to which the event pertains, which you could either zero out if there is no additional object, or use to store the address of a mutex or condition variable, etc.

    Declare and define appropriate constructors for that struct, which zero out fields that will not be used and takes values for the fields that will be used (depending on the kind of event being recorded), and initializes the struct accordingly. Please provide a default constructor that sets the event type to unknown and zeroes out all the other fields of the struct.

    Also declare and define a stand-alone overloaded insertion operator (operator<<) that takes a reference to an ostream and a reference to a const instance of that struct type, uses the ostream to print out the contents of the passed object, and then returns a reference to the ostream.

    In your main function, use the struct type and insertion operator to print out additional information when the main function has just been called and when it is just about to return (i.e., fill in the thread id and the address of the main function as the pointer to the currently executing function). Build and run your program, and as the answer to this exercise, please show your code and the output your program produced.

  4. Declare and define an event logger class with a public constructor that takes a reference to an ostream and uses it to initialize a reference-to-ostream member variable. Also declare and define a public default constructor that initializes the member variable to refer to cout

    Declare and define public register and unregister methods of the event logger class, through which output text for any kind of entity in the program can be registered (and deregistered) with the event logger. The register method should take a void * pointer and a reference to a const C++ style string, and should store it in a container member variable (e.g., a map or multimap depending on whether you want to allow registration of multiple strings of text for each entity). The unregister method should take a void * pointer and if it is in the container remove it (for a multimap or other similar data structure you should decide whether or not you want an unregister call to remove just one or all of the matching entries). Please feel free to declare and define additional overloads of those methods as appropriate, according to the different variations for how the fields of the struct from the previous exercise can be used.

    Write an overloaded member insertion operator (operator<<) for the event logger class that takes a reference to a const instance of the struct type, looks up whether each of the void * fields of the struct are registered with the event logger, and prints out the contents of the struct using the registered string for the value of a field if text was registered or just the pointer value if not. Essentially, this ostream operator should behave like the ostream operator from the previous exercise if none of the fields of the passed struct had strings registered for their values, but for fields that have strings registered for their values the operator should print out the registered string instead of (or perhaps in addition to) their values.

    Since the event logger is intended for use by multiple threads, please synchronize any of its methods as necessary, but also try to keep the degree of synchronization to a minimum so that it can operate in a mostly concurrent (and thus minimally interfering) manner with respect to the concurrency of the threads that may use it.

    In your main function, declare an event logger object, register the string "main" for the address of the main function, fill in a struct and then use the event logger object print out the contents of the struct (including the registered string for main) when the main function has just been called and when it is just about to return. Build and run your program, and as the answer to this exercise, please show your code and the output your program produced.

  5. Extend your event logger object with a register method that takes a thread id and an optional event count (default the event count to something large like 1024) as a parameter, and unless that thread id is already registered (1) remembers the thread id and a current event counter (set to 0 initially) for that thread, and (2) dynamically allocates and remembers the address of a buffer of event record structs (sized per the passed or defaulted event count to record). Also provide an unregister method that takes a thread id and removes the thread id and counter and removes and deletes its current event record buffer. Optionally, you can provide a reset method that takes a thread id and an event count and replaces the thread's current event record buffer with a new one of the specified size (and deletes the old buffer), and also resets the thread's current event counter to 0.

    Extend your event logger object with a record method that is overloaded to take thread id along with different combinations of other event record fields that may be relevant, and uses the passed parameters to construct (using the placement version of the new operator) or assign their values to the current position in that thread's event record buffer (according to the current event count) and then increment the event count (be careful not to exceed the end of the buffer - for example you may want to increment modulo the size of the buffer).

    Add a print method to the event logger that prints out all the events stored in it. You may want to record time stamps or a gloabl event counter in each event record so that the overall interleaving of events is visible in the printed output.

    Since the event logger is intended for use by multiple threads, again please synchronize any of its methods as necessary, but also try to keep the degree of synchronization to a minimum so that it can operate in a mostly concurrent (and thus minimally interfering) manner with respect to the concurrency of the threads that may use it - in particular you may or may not need to synchronize the record method depending on your design.

    Write and instrument a recursive function that computes the factorial of a passed number, which is called from your main function. Register the main thread, the main function, the recursive function, etc. in the event logger object and collect relevant events during its execution. After it finishes print out the events that were collected. Build and run your program, and as the answer to this exercise show the output traces from the events your main function recorded.

    Enrichment Exercises (Optional)

  6. Use the features you have developed in the previous exercises to instrument your code from a previous studio (especially the Monitor Object studio or another one of similar complexity) so that all of the different kinds of events are recorded in an event monitor and then subsequently printed out. One idea for how to do this in a thread safe manner is to have a static pointer to a logger object that is swapped out atomically from time to time, with a dedicated thread printing out the events from the most recently used event logger object, while a new event logger object is collecting events from the other threads. As the answer to this exercise, please show the event traces and comment on whether or not you see any thread safety issues based on the output.

  7. From the previous exercise, please introduce different kinds of races and synchronization hazards into your code, and repeat the previous evaluation. As the answer to this exercise, please show the event traces and comment on whether or not you see any thread safety issues based on the output.