lab_memory Malevolent Memories

Assignment Description

In this lab, you will learn about two memory checking utilities:
Valgrind and AddressSanitizer (aka ASAN).

The first utility you will learn about is Valgrind.
Valgrind will help you detect memory errors and practice implementing the big
three. Valgrind is an extremely useful tool for debugging memory problems and
for fixing segfaults or other crashes. This
lab is also particularly important because we will be checking for memory errors and leaks
on your assignments. You will lose points for memory leaks and/or memory errors (we will also
teach you the difference between a memory leak and a memory error). You should
check your code with Valgrind/ASAN before handing it in. You should also be aware that
Valgrind/ASAN will only detect a leak if your program allocates memory and then fails to
deallocate it. It cannot find a leak unless the code containing the leak is
executed when the program runs. Thus, you should be sure to test your code
thoroughly and check these tests with Valgrind/ASAN.

Valgrind and ASAN do not work well or at all on later versions of macOS! In particular,
we have noticed ASAN never reporting errors or errors, and Valgrind always reporting
memory leaks.

We strongly recommend working on this assignment on an EWS system

Lab Insight

As you can tell from the description, this lab will be heavily focused on memory management and good practices when dealing with memory management issues such as memory leaks. It is even possible to build lightweight tools similar to Valgrind, where these tools can trace the memory used by the program and report information related to memory management issues such as memory leaks. If this lab is interesting for you, CS 241 is a course that covers both how to write memory allocation models as well as how to develop tools similar to Valgrind in functionality.

Background on Valgrind

Valgrind is a useful tool to detect memory errors and memory
leaks.

Valgrind is a free utility for memory debugging, memory leak detection, and
profiling. It runs only on Linux systems. To prepare your project to be
examined by Valgrind you need to compile and to link it with the debug options
-g and -O0. Make sure your Makefile is using these options when
compiling. In order to test your program with Valgrind you should use the
following command:

valgrind ./yourprogram

To instruct valgrind to also check for memory leaks, run:

valgrind --leak-check=full ./yourprogram

You will see a report about all found mistakes or inconsistencies. Each row of
the report starts with the process ID (the process ID is a number assigned by
the operating system to a running program). Each error has a description, a
stack trace (showing where the error occurred), and other data about the error.
It is important to eliminate errors in the order that they occur during
execution, since a single error early could cause others later on.

Here is a list of some of the errors that Valgrind can detect and report. (Note
that not all of these errors are present in the exercise code.)

Invalid read/write errors.
This error will happen when your code reads or writes to a memory address which
you did not allocate. Sometimes this error occurs when an array is indexed
beyond its boundary, which is referred to as an “overrun” error. Unfortunately,
Valgrind is unable to check for locally-allocated arrays (i.e., those that are
on the stack.) Overrun checking is only performed for dynamic memory.

Example

int * arr = newint[6];
arr[10] = -5;

Use of an uninitialized value.
This type of error will occur when your code uses a declared variable before
any kind of explicit assignment is made to the variable.

Example

int x;
cout << x << endl;

Invalid free error.
This occurs when your code attempts to delete allocated memory twice, or delete
memory that was not allocated with new.

Example

int * x = newint;
delete x;
delete x;

Mismatched free() / delete / delete [].
Valgrind keeps track of the method your code uses when allocating memory. If it
is deallocated with different method, you will be notified about the error.

The number, 22153, represents the process id (PID) given to the program by the operating
system. Below is a break down of each error.

The first error, originating from line 17, is due to the fact that y was never
initialized

The second error comes from the fact that the wrong delete is being used in line 18.
From looking at the declaration of arr we can see that delete[] should’ve been
used instead.

The last error occurs on line 20 when trying to delete something that wasn’t
initialized or pointing to the heap.

Background on ASAN

ASAN is another utility used to check for memory-related errors. Unlike Valgrind, which
interprets code as it is run, ASAN is built into the executable at compile time. For this
reason, ASAN is sometimes faster than Valgrind. However, ASAN does not handle recursion as
well as Valgrind.

ASAN checks for a wide variety of things. Here are some things that may pop up
when debugging:

Out-of-bounds access to heap, stack, and globals.
This error occurs when you allocate some memory and then try to access a region
outside your allocated space.

Example

int * arr = newint[100];
return arr[100];

Use-after-free (dangling pointer reference).
This error occurs when you try to use memory you have already freed. It is
especially helpful when you have several pointers referring to the same memory.

Example

int * arr = newint[100];
delete [] arr;
return arr[5]; //NOPE

Heap buffer overflow.
This error occurs when you go out of bounds within an array created on the
heap.

A still reachable block happens when you forget to delete an object,
the pointer to the object still exists, and the memory for object is still
allocated.

A lost block is a little tricky. A pointer to some part of the block
of memory still exists, but it is not clear whether it is pointing to the
block or is independent of it.

A definitely lost block occurs when the block is not deleted but no
pointer to it is found.

Fixing Memory Bugs (using ASAN)

NOTE: You cannot
used Valgrind and ASAN at the same time. Make sure to run it only on the
non-ASAN executable!

Before fixing the bugs, you’ll need to compile the code. ASAN terminates the
program upon the first invalid memory access. So, if you have an invalid memory
access, you’ll have to fix it before moving on to other errors. This
incremental procedure should help you step through memory bugs one at a
time.

To compile the code for the lab, run

make

(In this and future assignments, make will produce 2 versions of each
executable: the “normal” version and a version that has ASAN enabled.)

This will create two executable files named allocate and allocate-asan. Run
the ASAN version with

./allocate-asan

which will check for both memory errors and leaks.

Once you have fixed all the memory errors, you can test your program output
using:

./allocate > output.txt
diff output.txt soln_output.txt

Note that most of the work in this lab consists of fixing ASAN’s errors and
memory leaks, rather than the program output, which should be correct already
once the memory errors are fixed.

Further Testing

To test the code further, you may use the provided test program to see whether your program runs fine without any memory leaks. We expect that your code produces no memory leaks for both the allocate program and the test program. To compile the test program, please run the following:

make test

You can test whether the test program successful works without any memory issues by running

valgrind ./test

If you notice no errors or memory leak errors outputted from Valgrind, your test program has successfully run as well. If not, it’s time to Valgrind your way through the code once more :)

To get more specific issues about memory leak releated issues with Valgrind, you may get more information by using:

valgrind --leak-check=full ./test

GDB: A Debugger

While ASAN and Valgrind tell you what went wrong in your program after it has been executed,
GDB is a debugging tool that allows you to see what is going on `inside’ your program WHILE
it executes! It can also be used after your program has crashed for debugging purposes as well.
In particular, Valgrind and ASAN work well for memory related errors, but GDB can handle crashes
from those as well as other classes of bugs, such as logic errors.
To learn how to use GDB for your lab and mps, visit this page:

This should update your directory to contain a new directory called
lab_memory.

Code Description

For this lab, you will be fixing bugs in course staff’s Student-To-Room
allocation program. Since many CS classes are very large (CS 225 has nearly 800 students!), exams are usually spread across several rooms. Before each exam, course staff have to allocate different students to different rooms, so that everyone can take the test with enough space.

For example, if there were only two classrooms of equal size, students in the
first half of the alphabet (last name letters A - N) might go to Siebel 1404,
while students in the second half of the alphabet (letters M - Z) might go to
DCL 1320.

However, with more rooms, this problem becomes more difficult. In the sample
situation provided, there are 9 classrooms for the exam, varying in seating
capacity from 43 to 70 seats (i.e. 21 to 35 students seated every-other-desk).
Although we’ll have to break up the alphabet more, we’d still like to assign
students with the same first letter of their last name to the same room, as
this makes going to the right room easier.

We’ve provided you the code to solve this problem, however, it has several
memory bugs in it. You’ll have to use Valgrind and/or ASAN, as well as some debugging skills
from lab_debug, to find the bugs and fix
them. Note, there are no bugs in the fileio namespace.