Skip to main content

Segmentation faults and other memory bugs (reading uninitialized memory, reading/writing beyond the bounds of an array, memory leaks, etc.) can be hard to track down with a traditional debugger. Memory errors can be elusive, and may not cause the program to crash immediately. A program with memory errors may even appear to work correctly on some datasets or on some machines.

We recommend using a special debugger to find memory errors, for example the open-source software tools Dr. Memory or Valgrind. Commercial versions of these tools include PurifyPlus and Parasoft Insure++.

We'll discuss the Dr. Memory and Valgrind memory debugging tools and the memory error reports these tools produce at the end of Lecture 6 and during Lab 4. You'll be expected to use one of these tools for debugging starting with Homework 3. The homework submission server and the TAs will use these tools for grading your homework.

Note that running a program under Dr. Memory or Valgrind will slow the execution time significantly. The final step to check for memory leaks is especially costly. We recommend starting with the the smallest tests/input file, even if those appear to be working ok. Fix any memory errors or leaks that are found, then work your way to largest test cases, and be patient.

Installation Options by Operating System & Hardware

Dr. Memory

Dr. Memory is available for GNU/Linux, Microsoft Windows, and older Intel-chip-based MacOS operating systems. For questions, bug reports, and discussion, use the Dr. Memory Users group: http://groups.google.com/group/drmemory-users

Please report issues with Dr. Memory to the Dr. Memory Users Group by email: drmemory-users@googlegroups.com. Be sure to include details about your operating system and the Dr. Memory version number. Don't send your full homework submission (it is a public mailing list).

Dr. Memory on GNU/Linux or Windows Subsystem for Linux (WSL)

  1. Obtain the most recent Dr. Memory tar.gz file for your operating system from:
    https://github.com/DynamoRIO/drmemory/releases/

    In the example below, we assume DrMemory-Linux-2.6.20282.tar.gz is the most recent release.

    Type these commands into your terminal:
     

      cd
    
      wget https://github.com/DynamoRIO/drmemory/releases/download/cronbuild-2.6.20282/DrMemory-Linux-2.6.20282.tar.gz
    
      tar -xvzf DrMemory-Linux-2.6.20282.tar.gz
    
     
  2. [OPTIONAL] Add Dr. Memory to your PATH by typing this at the WSL bash prompt:

      echo 'PATH=$PATH:~/DrMemory-Linux-2.6.20282/bin64' >> ~/.bashrc
    
      source ~/.bashrc
    

    NOTE: Do not attempt to edit WSL system files from Windows. The permissions and filesystem will get messed up and you'll need to reinstall everything to recover.

     
  3. In order to use Dr Memory, you must first re-compile / re-build your application with debug information by using the -g option. For example:

      g++ -g main.cpp foo_main.cpp foo_other.cpp -o foo.out
    

    NOTE: Replace 'main.cpp foo_main.cpp foo_other.cpp' with the names of the C++ source code file(s) for your project, and replace 'foo.out' with your desired executable name.

     
  4. Run your program under Dr. Memory:

    Using the full path to the Dr. Memory:

      ~/DrMemory-Linux-2.6.20282/bin64/drmemory -brief -- foo.out arg1 arg2
    

    If you added Dr. Memory to your path (the OPTIONAL step above), you can use this shortcut instead:

      drmemory -brief -- foo.out arg1 arg2
    

    NOTE: Replace 'foo.out' with your executable name and replace 'arg1 arg2' with your desired command line arguments.

     
  5. Dr. Memory will report errors to the screen as it runs. It will print a summary at the end of what it found.

Dr. Memory on Intel-chip MacOS

If you have a newer ARM Silicon Mac, follow the instructions to use Valgrind within an Ubuntu Docker Container below.

  1. Obtain the most recent Dr. Memory tar.gz file for your operating system from:
    https://github.com/DynamoRIO/drmemory/releases/

    In the example below, we assume DrMemory-Linux-2.6.19800.tar.gz is the most recent release.

  2. Save the package to a directory of your choice. Then untar the package by typing:

  3.   tar -xvzf DrMemory-MacOS-2.6.19800.tar.gz
    

  4. Be sure to build your application with debug information, the -g option, so you get line numbers. For example:

  5.   g++ -g main.cpp foo_main.cpp foo_other.cpp -o foo.out
    

  6. Run your program under Dr. Memory, replacing foo.out arg1 arg2 with your executable name and any command line arguments:

  7.   ~/DrMemory-MacOS-2.6.19800/bin64/drmemory -brief -- ./foo.out arg1 arg2
    

  8. Dr. Memory will report errors to the screen as it runs. It will print a summary at the end of what it found.

  9. OPTIONAL: Edit your ~/.zshrc file to add the location of the DrMemory executable to your PATH.

    Open this file, or create an empty file if you have none. Edit your PATH variable to insert full path for the DrMemory bin64 directory. The PATH is the sequence of directories (separated by ':') that are checked when you run a program. For example:

      export PATH=$HOME/bin:/usr/local/bin:/Users/INSERT-YOUR-USERNAME/DrMemory-MacOS-2.6.19800/bin64/
    

    Make sure to replace INSERT-YOUR-USERNAME and DrMemory-YourOperatingSystem-VersionXX. Close and re-open your terminal. Then you can simply type:

      drmemory -brief -- foo.out arg1 arg2
    

Installing Dr. Memory on native Windows (not Windows Subsystem for Linux (WSL))

  1. Obtain Dr. Memory. To easily place it on the system path, use the installer (the .msi file). Alternatively, you can instead obtain the .zip file for a local install.
    https://github.com/DynamoRIO/drmemory/wiki/Downloads

  2. Double click on the .msi file to run the installer. Click Next.

    Check the box to accept the license and click Next.

    The default location for Dr. Memory installation is fine (it's probably C:\Program Files (x86)\Dr. Memory\ for 64-bit Windows). Click Next.

    Then click Install. You'll be asked to confirm that you want to make administrative changes to the machine.

    After a quick installation, press Finish.

  3. Follow the instructions below to compile & run your program using the Visual Studio IDE or the Visual Studio Command Prompt.

Dr. Memory and Visual Studio

You can use Dr. Memory with the Microsoft Visual Studio compiler:

  1. Build your application as 32-bit with Visual Studio (32-bit is the default). Be sure to include debug information. You can verify that you are including debug information by looking at the properties of your build target:

    Press Alt-F7 to bring up the configuration properties. Under "Configuration Properties | C/C++ | General", the "Debug Information Format" entry should either say "Program Database (/Zi)" or "Program Database for Edit and Continue (/ZI)". Additionally, under "Configuration Properties | Linker | Debugging", the "Generate Debug Info" entry should say "Yes (/DEBUG)". For Visual Studio 2015, under "Configuration Properties | Linker | Debugging", the "Generate Debug Info" entry should say "Optimize for debugging (/DEBUG)" -- it should not say "Optimize for faster linking (/DEBUG:FASTLINK)".

  2. Disable Runtime Checks: The Visual Studio compiler's /RTC1 flag can prevent Dr. Memory from reporting uninitialized reads of local variables, and the /RTC1 checks for uninitialized reads themselves may not catch everything that Dr. Memory finds. However, /RTC1 does perform additional stack checks that Dr. Memory does not, so for best results, your application should be run under Dr. Memory without /RTC1, and run natively (for development & testing without Dr. Memory) with /RTC1.

    In the Visual Studio IDE, press Alt-F7 and then under "Configuration Properties | C/C++ | Code Generation" ensure "Basic Runtime Checks" says "Default".

  3. The most recent Dr. Memory installer (for version 1.8 and later) configures Dr. Memory as a Visual Studio "External Tool", which adds a new menu item allowing you to run Dr. Memory within the IDE.

    Now you can select the "Tools | Dr. Memory" menu item and Visual Studio will run your application under Dr. Memory. You can add arguments to your application in the box that pops up immediately after selecting the men item by adding them at the end, after "$(TargetPath)".

  4. The output of Dr. Memory (along with your program) will be printed to the Visual Studio Output Window. Dr. Memory will report errors to the screen as it runs. It will print a summary at the end of what it found. You can double-click on a source file on any error's callstack frame in order to automatically open up that file to the line number indicated.

Using the Visual Studio compiler without the Visual Studio Integrated Development Environment (IDE)

  1. Launch the Visual Studio Command Prompt. From the Start menu, under All Programs, find your Visual Studio version (e.g., 2010) and expand it. Then expand Visual Studio Tools. Select the "Visual Studio 2010 Command Prompt". (You don't want the x64 or Cross Tools versions.) Note: this is not the Cygwin shell.

    This Command Prompt is a cmd shell in which a batch file that comes with Visual Studio has been executed. This batch file is called vcvars.bat and it sets up the path and environment variables needed to run the compiler from the command line.

    Note: You can extract the environment variables from the batch file and set them up in your .bashrc so you can build from a shell.

  2. At the command line, change to the directory containing your source files.

  3. Run the compiler, which is called "cl". This will build hw.exe from all .cpp files in the current directory:

  4.   cl /Zi /MT /EHsc /Oy- /Ob0 /Fehw.exe *.cpp
    

  5. If you installed Dr. Memory before you opened the Command Prompt, you can run drmemory from the same prompt. Run this command, replacing foo.exe arg1 arg2 with your executable name and any command line arguments:

  6.   drmemory -brief -batch -- foo.exe arg1 arg2
    

    If you don't see any extra output from Dr. Memory as your program runs, remove the -batch flag and the Dr. Memory output will be sent to a file and notepad will launch automatically to display this file.

      drmemory -brief -- foo.exe arg1 arg2
    

  7. Dr. Memory will print a summary at the end of what errors it found.

Valgrind

Valgrind only works on Unix-based systems (e.g., GNU/Linux, FreeBSD, and WSL).

Unfortunately, Valgrind does not work on more recent versions of MacOS (it only runs on MacOS 10.12 or earlier). You can however use Valgrind within an Ubuntu Docker container on a Mac!

Valgrind does not work on Cygwin because Cygwin emulates UNIX at the library layer, but Valgrind operates at the system call layer and the Windows system calls are significantly different than UNIX system calls.

To use Valgrind...

  1. Valgrind is installed by default on most Linux distributions.

  2. Your program should be compiled with debug information enabled by specifying the -g flag. For example:

  3.   g++ -g main.cpp foo_main.cpp foo_other.cpp -o foo.out 
    

  4. Then run the program by adding Valgrind to the beginning of your command line (replace foo.out arg1 arg2 with your program name and any command line arguments for your program):

  5.   valgrind --leak-check=full --show-reachable=yes ./foo.out arg1 arg2
    

    If that example run of your program contains any memory errors Valgrind will output information to help you track down the error. Note that using Valgrind can significantly slow down execution time as it inspects every memory action. You may need to craft a smaller test case that exhibits the same bug you would like to solve.

Note: Because some STL classes (including string) use their own allocators (and do other optimization tricks), there may be a warning about memory that is ``still reachable'' even though you've deleted all your dynamically allocated memory. The newer versions of Valgrind automatically suppresses some of these common false positive errors, so you may see this listed as a ``suppressed leak''.

Using Valgrind within a Docker Container on a M-series ARM Silicon Mac

  1. Install Docker Desktop
    Click the link for "Docker Desktop for Mac with Apple Silicon" and install this software.
    You will drag & drop the program into your Applications folder.

    Go to your Applications folder and double click on the Docker application to finish installation. You will be asked to accept the service agreement, you can use the recommended settings, and you should type your computer password when prompted. You can choose "do not allow" when asked about giving the program network access.

  2. Each time you want to run Valgrind, Start the Docker engine.
    Go to your Applications folder and double-click on "Docker.app" to start the Docker engine. The Docker Desktop application will launch a window.

    The first time you use the Docker app, it will ask if you want to make a Docker account, but you do not need to make an account, you can press "skip" in the upper right corner.

  3. Next use the terminal command line to Download/clone a docker image with the Linux Ubuntu 22.04 operating system that includes a C++ compiler and Valgrind. NOTE: This is the docker image we use for autograding your homework on Submitty. Go to a terminal on your host MacOS machine and type:

      docker pull submittyrpi/csci1200:latest 
    

    Note: The first time you run a docker command from the terminal, you will be asked to allow it to access data from other apps. You should say "allow".

    Confirm the image was downloaded by typing:

      docker image ls
    

    It should say something like this:

      REPOSITORY             TAG       IMAGE ID       CREATED       SIZE
      submittyrpi/csci1200   latest    xxxxxxxxxxx    xxxxxxxxx     2.xxGB
    

  4. When you want to run the Valgrind memory debugger on a specfic C++ program, open a terminal on your host MacOS computer and 'cd' to the directory containing your C++ source code files for that program. Type 'ls' to confirm that you are in the directory with your files.

  5. From that terminal, Create and run a docker container using the docker image downloaded/cloned above. The command below shares the files from the current directory on the MacOS host computer with the /tmp/ds directory in the Ubuntu docker container. It also sets the initial working directory to be /tmp/ds.

      docker run -v .:/tmp/ds -w /tmp/ds -i -t submittyrpi/csci1200:latest bash 
    

    After a pause to start the docker container, you should get a prompt for the Ubuntu docker terminal, for example:

      root@xxxxxxxxx:/tmp/ds
    

  6. Type ls to confirm you can see the source files for your program you want to debug.

  7. Recompile your source code. This step is important each time you switch environments. An executable compiled on your host MacOS operating system will not run on the Ubuntu container. And vice versa, an executable compiled for Ubuntu will not run on MacOS. Don't forget the -g command, it is necessary to compile with debug information to get quality error messages from the Valgrind debugger.

      g++ myfile1.cpp myfile2.cpp -Wall -Wextra -g -o my_executable.out
    

    NOTE: You may need to specify that your compiler prepare an older version of the DWARF debug info format. If you see errors related to dwarf2 when you run valgrind, use the -gdwarf-2 option:

      g++ myfile1.cpp myfile2.cpp -Wall -Wextra -gdwarf-2 -o my_executable.out
    

  8. Run your program without Valgrind.
    Confirm that you see similar behavior with your program running in Ubuntu that you saw when you ran the program on MacOS.

      ./my_executable.out arg1 arg2
    

  9. Run your program with Valgrind.
    Prepend your run command with valgrind. The optional extra arguments
    '--leak-check=full --show-reachable=yes' will show more details about the errors.

      valgrind ./my_executable.out arg1 arg2
      valgrind --leak-check=full --show-reachable=yes ./my_executable.out arg1 arg2
    

    You should see information for any memory errors and memory leaks that are detected by Valgrind as the program runs. Edit your code, recompile, and re-run under Valgrind to fix any problems.


  10. When you are finished debugging, you can Exit and cleanup the Ubuntu docker container by typing at the Ubuntu container prompt:

      exit
    

    You can Stop the Docker engine by selecting "Quit Docker Desktop" from the Docker Desktop App menu. Note that pressing "Command-Q" shortcut may not cleanly stop the containers & engine.

    You can verify that the Docker engine is halted by running 'docker image ls' in a terminal. It should print a message like:

      Cannot connect to the Docker daemon at unix:///Users/xxxxxxxx/.docker/run/docker.sock. Is the docker daemon running?
    

Suppression of False Positives in Valgrind

If you see false positive error messages in Valgrind, you may want to create an error suppression file to allow you to focus on your actual errors.

  1. Add the --gen-suppressions=all option to the valgrind command line:

  2.   valgrind --leak-check=full --gen-suppressions=all ./foo.out arg1 arg2
    

  3. For each false positive (an error not obviously pointing at your code), copy-paste the suppression text (a block of text in curly braces) into a new file containing your custom suppressions, let's call it my_suppressions.txt.

  4. To use that suppression file when you run Valgrind:
  5.   valgrind --leak-check=full --suppressions=my_suppressions.txt ./foo.out arg1 arg2
    

  6. You may need to add to that file in the future, when you use additional library functions that cause different false positive errors.

Read more about Valgrind suppressions here:
http://valgrind.org/docs/manual/manual-core.html#manual-core.suppress