p1-stats

Address Sanitizer

This tutorial will compile your code with an Address Sanitizer enabled. The Address Sanitizer is very good at finding memory errors, including going off the end of an array or vector.

If you’re interested, here is more information about the Address Sanitizer, and a comparison of Address Sanitizer vs Valgrind.

Quick Start

If you’re revisiting this tutorial, here’s the TL;DR. If you’re new to Address Sanitizers, skip this section.

WSL or Linux: Add compiler flags -fsanitize=address, -fsanitize=undefined, and -D_GLIBCXX_DEBUG. For example:

CXXFLAGS = --std=c++11 -Wall -Werror -pedantic -g -fsanitize=address -fsanitize=undefined -D_GLIBCXX_DEBUG

macOS: Add compiler flags -fsanitize=address and -fsanitize=undefined. For example:

CXXFLAGS = --std=c++11 -Wall -Werror -pedantic -g -fsanitize=address -fsanitize=undefined

Visual Studio: Address sanitizer is enabled by default.

Xcode: Enable the address sanitizer and undefined behavior sanitizer with this (tutorial).

Prerequisites

We’re assuming that you already have a folder with source code in it, e.g., p1-stats/.

$ pwd
/Users/awdeorio/src/eecs280/p1-stats
$ ls
Makefile      main_test.out.correct  p1_library.h  stats_public_test.cpp
main.cpp      main_test_data.tsv     stats.cpp     stats_tests.cpp
main_test.in  p1_library.cpp         stats.h

Example without Address Sanitizer

Let’s do an example without an address sanitizer. We’ll make a mistake on purpose in main.cpp, going off the end of a vector.

#include <iostream>
#include <vector>
using namespace std;

int main() {
  cout << "hello from main!\n";
  vector<int> v = {0, 0};  // v contains 2 items
  v.pop_back();            // v contains 1 item
  cout << v[1] << "\n";    // off the end of v
}

Clean, build and run. Notice that the output of v[1] is 0, however, this operation is undefined because it’s off the end of the vector!

$ make clean
rm -rvf *.exe *~ *.out *.dSYM *.stackdump
$ make main.exe
g++ -Wall -Werror -pedantic -g --std=c++11 main.cpp stats.cpp p1_library.cpp -o main.exe
$ ./main.exe 
hello from main!
0

Optionally, try to find the error with Valgrind. It doesn’t find the error.

$ valgrind ./main.exe 
==28736== Memcheck, a memory error detector
==28736== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==28736== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==28736== Command: ./main.exe
==28736== 
hello from main!
0
==28736== 
==28736== HEAP SUMMARY:
==28736==     in use at exit: 0 bytes in 0 blocks
==28736==   total heap usage: 1 allocs, 1 frees, 8 bytes allocated
==28736== 
==28736== All heap blocks were freed -- no leaks are possible
==28736== 
==28736== For counts of detected and suppressed errors, rerun with: -v
==28736== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

WSL or Linux command line

The default compiler on most Linux systems, including the Windows Subsystem for Linux (WSL) is GNU GCC. Verify that you’re using GCC version 4.9 or higher. Your version might be different.

$ g++ --version
g++ (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0

WARNING: The address sanitizer will not work on heavily resource limited system such as CAEN Linux.

Before we build, we need to modify the compiler flags. Edit your Makefile and change the compiler flags to add -fsanitize=address and -fsanitize=undefined and -D_GLIBCXX_DEBUG. You might also be interested in -fsanitize=leak, -fno-sanitize-recover and -fstack-protector.

CXXFLAGS = --std=c++11 -Wall -Werror -pedantic -g -fsanitize=address -fsanitize=undefined -D_GLIBCXX_DEBUG

Build.

$ make clean
rm -rvf *.exe *~ *.out *.dSYM *.stackdump
$ make main.exe
g++ --std=c++11 -Wall -Werror -pedantic -g -fsanitize=address -fsanitize=undefined main.cpp stats.cpp p1_library.cpp -o main.exe

Run. The sanitizer detects the out-of-bounds problem and prints a stack trace.

$ ./main.exe
hello from main!
/usr/include/c++/9/debug/vector:427:
In function:
    std::__debug::vector<_Tp, _Allocator>::reference 
    std::__debug::vector<_Tp, 
    _Allocator>::operator[](std::__debug::vector<_Tp, 
    _Allocator>::size_type) [with _Tp = int; _Allocator = 
    std::allocator<int>; std::__debug::vector<_Tp, _Allocator>::reference = 
    int&; std::__debug::vector<_Tp, _Allocator>::size_type = long unsigned 
    int]

Error: attempt to subscript container with out-of-bounds index 1, but 
container only holds 1 elements.

Objects involved in the operation:
    sequence "this" @ 0x0x7ffc1eaa7e80 {
      type = std::__debug::vector<int, std::allocator<int> >;
    }
Aborted (core dumped)

macOS command line

The default compiler on macOS is Clang/LLVM. Verify that you’re using Clang. Your version might be different.

$ g++ --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 9.0.0 (clang-900.0.39.2)
Target: x86_64-apple-darwin16.7.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin

Before we build, we need to modify the compiler flags. Edit your Makefile and change the compiler flags to add -fsanitize=address and -fsanitize=undefined.

CXXFLAGS = --std=c++11 -Wall -Werror -pedantic -g -fsanitize=address -fsanitize=undefined

Build.

$ make clean
rm -rvf *.exe *~ *.out *.dSYM *.stackdump
$ make main.exe
g++ --std=c++11 -Wall -Werror -pedantic -g -fsanitize=address -fsanitize=undefined main.cpp stats.cpp p1_library.cpp -o main.exe

Run. The sanitizer detects the out-of-bounds problem and prints a stack trace.

$ ./main.exe
=================================================================
==95600==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6020000000f4 at pc 0x00010c799eca bp 0x7fff53466cd0 sp 0x7fff53466cc8
READ of size 4 at 0x6020000000f4 thread T0
    #0 0x10c799ec9 in main main.cpp:12
    #1 0x7fffdcd52234 in start (libdyld.dylib:x86_64+0x5234)

0x6020000000f4 is located 4 bytes inside of 8-byte region [0x6020000000f0,0x6020000000f8)
allocated by thread T0 here:
    #0 0x10c8420ab in wrap__Znwm (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x640ab)
    #1 0x10c79c319 in std::__1::vector<int, std::__1::allocator<int> >::allocate(unsigned long) vector:925
    #2 0x10c799590 in main main.cpp:10
    #3 0x7fffdcd52234 in start (libdyld.dylib:x86_64+0x5234)

SUMMARY: AddressSanitizer: heap-buffer-overflow main.cpp:12 in main
Shadow bytes around the buggy address:
  0x1c03ffffffc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1c03ffffffd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1c03ffffffe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1c03fffffff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1c0400000000: fa fa fd fd fa fa fd fd fa fa 00 00 fa fa 00 06
=>0x1c0400000010: fa fa 00 04 fa fa 00 00 fa fa 00 06 fa fa[04]fa
  0x1c0400000020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c0400000030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c0400000040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c0400000050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c0400000060: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==95600==ABORTING
hello from main!

Visual Debuggers

Visual Studio

Visual Studio provides an address sanitizer with bounds checking automatically with every debug build. You don’t need to do anything different!

Xcode

If you’re using Xcode, you can enable the address sanitizer and undefined behavior sanitizer when you run or debug within Xcode (tutorial).

VS Code, Emacs and other text editors

Just build and run in the terminal.

Next steps

Return to the main set up tutorial.

Pro-tips

Here’s a way to automatically add the correct compiler flags on macOS or Windows/WSL/Linux. Edit the top of your Makefile with the following code, which will figure out the OS and append the correct compiler flags.

CXX ?= g++
CXXFLAGS ?= --std=c++11 -Wall -Werror -pedantic -g

# Add sanitizer flags for identifying undefined behavior.  The flags are
# different on macOS (AKA Darwin) and Windows/WSL/Linux.
UNAME := $(shell uname -s)
ifeq "$(UNAME)" "Darwin"
	CXXFLAGS += -fsanitize=address -fsanitize=undefined
else
	CXXFLAGS += -fsanitize=address -fsanitize=undefined -D_GLIBCXX_DEBUG
endif