Using AddressSanitizer (ASan) in a CMake project
AddressSanitizer (ASan) [1] is a memory error detector for the C and C++ languages. It has been included in compilers such as GCC and Clang for quite some time now. Although the official Wiki page provides a very straightforward guide on how to use ASan in a general case, I could not find a good example of ASan being used in a CMake project. This is what this post is about.
Example project
Throughout this post I rely on a short example project ASanDemo
coded in C. It
implements two functions (see files asan-demo.h
and asan-demo.c
) used in
such a way as to trigger errors in ASan (see file main.c
).
The project has the following file structure.
ASanDemo ├── build ├── include │ └── asan-demo.h ├── src │ ├── asan-demo.c │ └── main.c └── CMakeLists.txt
Implementation
C source code
Header file
The header file asan-demo.h
defines two functions:
multiply
, a simple 32-bit integer multiplication function to demonstrate an integer overflow error,
#ifndef __ASAN_DEMO_H #define __ASAN_DEMO_H #include <stdio.h> #include <stdlib.h> int multiply(int, int);
leaker
, a function that allocates memory but does not free it before returning.
void leaker(void); #endif
Sources
Then, the associated source file asan-demo.c
implements these functions.
#include "asan-demo.h" int multiply(int a, int b) { return a * b; } void leaker(void) { void * buffer = malloc(256); }
Finally, from the main function in main.c
, I call both of the functions in
such a way as to trigger errors in ASan.
#include "asan-demo.h" int main(void) { // 32-bit integer overflow int result = multiply(4000000, 4000000); printf( "4,000,000 x 4,000,000 -> %d (computed) vs. 16,000,000,000,000 " "(expected)\n", result ); // Memory leak leaker(); return 0; }
CMake build configuration
There comes the most interesting part of the post, the CMake build configuration of our small example project.
I begin by defining the minimum CMake version required for the project.
cmake_minimum_required(VERSION 3.1)
Then, I declare the project itself.
project(asan-demo C)
The HEADERS
and SRC
variables shall hold the paths to all the header and
source files respectively.
set(HEADERS "include/asan-demo.h" ) set(SRC "src/asan-demo.c" "src/main.c" )
I instrument the compiler (GCC in this case) to produce an executable called
asan-demo
from the sources listed above.
add_executable(asan-demo ${SRC} ${HEADERS})
I also need to add include
to the list of include folders of the project.
target_include_directories(asan-demo PUBLIC "include")
Eventually, I enable ASan if the build type is set to Debug
. I do not want to
use ASan in the final release as it may degrade the application's performance.
The compilation and linking options associated to ASan here are:
-fsanitize=undefined
enabling the detection of undefined behavior such as integer overflows,-fsanitize=address
enabling the detection of memory problems such as leaks.
See [1] for additional information about the possibilities of the tool.
if(CMAKE_BUILD_TYPE MATCHES "Debug") set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror -fsanitize=undefined -fsanitize=address" ) target_link_options(asan-demo BEFORE PUBLIC -fsanitize=undefined PUBLIC -fsanitize=address ) endif()
Download, build and test
You can download the source files of the example project either separately or all at once (including the folder structure) as a tarball via the links below.
Ensure you have restored the file structure of the project as described in
Section Example project, navigate to the build
folder and run the
following commands to configure, build and execute the project.
cmake -DCMAKE_BUILD_TYPE="Debug" ..
make
./asan-demo
To configure the project without ASan, change the build type to something else
than Debug
, e.g. Release
.
cmake -DCMAKE_BUILD_TYPE="Release" ..
Note that, Release
is also the default build type.
cmake ..
Miscellaneous
As a side note, I use the ELisp code below to tangle (extract) the source code from this post, produce the output C and CMake source files and create the tarball containing the entire example project at the end.
(require 'org) (org-babel-tangle-file "using-address-sanitizer-asan-in-a-cmake-project.org") (shell-command "tar -cvJf ./attachments/ASan.tar.xz ./attachments/ASan")