Skip to content

Instantly share code, notes, and snippets.

@etiennecollin
Last active January 29, 2024 20:45
Show Gist options
  • Save etiennecollin/88f5f0c525bc5c25bd51e5673759f30f to your computer and use it in GitHub Desktop.
Save etiennecollin/88f5f0c525bc5c25bd51e5673759f30f to your computer and use it in GitHub Desktop.
Memory leaks analysis for C on macOS, Windows and Linux

Memory Leaks Analysis for C on macOS, Windows and Linux

Here, two different methods are presented to analyse memory leaks in C code.

The first method is cross-platform. It uses a small Docker image to both run Valgrind on the code and execute the code with the AddressSanitizer.

The second method is exclusive to macOS. It runs leaks on the code and is a bit simpler than the first method in that it does not use Docker. leaks should be as performant as Valgrind (although Valgrind has more features and could potentially catch memory leaks that leaks cannot catch).


Table of Contents


Using a Minimal Docker Image (Valgrind & AddressSanitizer)

Setup/Installation

  1. Append the content of the canalysis.sh file to your ~/.zshrc, ~/.bashrc, or equivalent file for your shell
    • For zsh, this can be done with cat canalysis.sh >> ~/.zshrc
  2. Install Docker
    • With Homebrew using brew install docker, or the package manager of your choice
    • Through the official website
  3. Place the Dockerfile in the directory of your choice
  4. While in the directory containing the Dockerfile, execute: docker build -t "canalysis" .
    • The Docker image will initialize and download the dependencies

Usage

The syntax for the new canalysis command is as follows:

canalysis <file1.c> <file2.c> <file3.c> <...>

All of the files passed as arguments will be directly given to clang to be compiled. Note that the generated executable will have the name of <file1>.

canalysis can be used in your terminal from any directory to automatically and properly compile the .c files passed as arguments, run Valgrind on the generated executable and execute the code with the AddressSanitizer.

The output of the analyses will be found in:

  • ./analysis/sanitizer_output.txt
  • ./analysis/valgrind_output.txt.

Uninstalling

  1. Remove the appended function from your ~/.zshrc, ~/.bashrc, or equivalent file for your shell
  2. Remove the file Dockerfile
  3. Remove the Docker image
    • The following command should work: docker rmi canalysis, but if it doesnt' force it with docker rmi -f canalysis

Using a Leaks (macOS only)

Setup/Installation

Append the content of the cleaks.sh file to your ~/.zshrc, ~/.bashrc, or equivalent file for your shell

  • For zsh, this can be done with cat cleaks.sh >> ~/.zshrc

Usage

The syntax for the new cleaks command is as follows:

cleaks <file1.c> <file2.c> <file3.c> <...>

All of the files passed as arguments will be directly given to clang to be compiled. Note that the generated executable will have the name of <file1>.

cleaks can be used in your terminal from any directory to automatically and properly compile the .c files passed as arguments and run leaks on the generated executable.

The output of the analysis will be found in:

  • ./analysis/leaks_output.txt

Uninstalling

Remove the appended function from your ~/.zshrc, ~/.bashrc, or equivalent file for your shell

# Author: Etienne Collin
# Date: 2024/01/16
# Email: collin.etienne.contact@gmail.com
function canalysis {
########################
# To be used with a Docker image
# Dockerfile contents:
# FROM alpine:latest
# RUN apk add clang valgrind compiler-rt llvm17
#
# Create the image:
# docker build -t "canalysis" .
#
# To delete the image:
# docker rmi -f canalysis
########################
# Get arguments
files="$@"
# Check that there are arguments
if [ -z "$files" ]; then
echo "Please input the .c files as arguments."
echo "The first argument should be the main file."
return 0
fi
# Verify that all the arguments are files with a .c extension
for file in $files; do
if [ ! -f "$file" ] || [ "${file##*.}" != "c" ]; then
echo "Please only input existing files with a .c extension."
return 0
fi
done
# Verify that the Docker daemon is running
if [ ! "$(docker stats --no-stream 2>/dev/null)" ]; then
echo "Please start the Docker daemon. Either run the Docker application or start it manually."
return 0
fi
# Verify that the Docker image exists
if [ "$(docker image inspect -f {{.Os}} canalysis)" != "linux" ]; then
echo "Docker image does not exist."
echo "Please create it with the following command:"
echo "> echo \"FROM alpine:latest\\\nRUN apk add clang valgrind compiler-rt llvm17\\\n\" > Dockerfile"
echo "Then build it with:"
echo "> docker build -t \"canalysis\" ."
echo "And, if you need to, delete it with:"
echo "> docker rmi -f canalysis"
return 0
fi
# Create the output directory if it does not exist
mkdir -p analysis
# Get the basename of the first file
filename="$(basename $1)"
filename="${filename%.*}"
# Command to attach to the Docker image
docker_command="docker run -it -v $PWD:/tmp -w /tmp canalysis"
########################
# Analyse with the sanitizer
########################
# Command to compile the .c files
compile_command="clang -Wall -O0 -g -fsanitize=address -fno-optimize-sibling-calls -fno-omit-frame-pointer -o analysis/$filename $files"
# Command to convert the addresses to symbols
symbolize_command="export ASAN_SYMBOLIZER_PATH=\$(which llvm-symbolizer); llvm-symbolizer ./$filename &>/dev/null"
# Combine the commands and execute them
full_command="/bin/sh -c \"\$compile_command; cd analysis; \$symbolize_command; ./$filename\""
eval $docker_command $full_command &> analysis/sanitizer_output.txt
# Print the output
echo "Sanitizer output -> analysis/sanitizer_output.txt"
########################
# Analyse with valgrind
########################
# Command to compile the .c files
compile_command="clang -Wall -O0 -g -o analysis/$filename $files"
# Command to run valgrind on the executable
valgrind_command="valgrind -s --leak-check=full --leak-resolution=med --track-origins=yes --track-fds=yes --trace-children=yes --show-leak-kinds=all --vgdb=no ./$filename"
# Combine the commands and execute them
full_command="/bin/sh -c \"\$compile_command; cd analysis; \$valgrind_command\""
eval $docker_command $full_command &> analysis/valgrind_output.txt
# Print the output
echo "Valgrind output -> analysis/valgrind_output.txt"
}
# Author: Etienne Collin
# Date: 2024/01/16
# Email: collin.etienne.contact@gmail.com
function cleaks {
# Get arguments
files="$@"
# Check that there are arguments
if [ -z "$files" ]; then
echo "Please input the .c files as arguments."
echo "The first argument should be the main file."
return 0
fi
# Verify that all the arguments are files with a .c extension
for file in $files; do
if [ ! -f "$file" ] || [ "${file##*.}" != "c" ]; then
echo "Please only input existing files with a .c extension."
return 0
fi
done
# Create the output directory if it does not exist
mkdir -p analysis
# Get the basename of the first file
filename="$(basename $1)"
filename="${filename%.*}"
# Compile the .c files
clang -Wall -O0 -g -o "analysis/$filename" $files &> analysis/leaks_output.txt
cd analysis
# Make the process debuggable
# Check if the tmp.entitlements file exists
if [ ! -f "tmp.entitlements" ]; then
/usr/libexec/PlistBuddy -c "Add :com.apple.security.get-task-allow bool true" tmp.entitlements &>/dev/null
fi
codesign -s - --entitlements tmp.entitlements -f "$filename" &>/dev/null
# Start malloc logging
export MallocStackLogging=1
export MallocScribble=1
# Run leaks on the executable
leaks -quiet -groupByType -conservative -atExit -- "./$filename" &>> leaks_output.txt
# Stop malloc logging
unset MallocStackLogging
unset MallocScribble
# Print the output
cd ..
echo "Leaks output -> analysis/leaks_output.txt"
}
FROM alpine:latest
RUN apk add clang valgrind compiler-rt llvm17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment