diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e21466..1a6051f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,6 +58,9 @@ project(libaaruformat C) # Option to enable slog logging (disabled by default) option(USE_SLOG "Enable slog logging" OFF) +# Option to enable address sanitizer (disabled by default) +option(USE_ASAN "Enable address sanitizer" OFF) + # Set C standard set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) @@ -141,6 +144,23 @@ if("${CMAKE_BUILD_TYPE}" MATCHES "Release") endif() endif() +# Address sanitizer configuration +if(USE_ASAN) + message(STATUS "Address sanitizer enabled") + + if("${CMAKE_C_COMPILER_ID}" MATCHES "MSVC") + # MSVC address sanitizer flags + add_compile_options(/fsanitize=address) + add_link_options(/fsanitize=address) + else() + # GCC/Clang address sanitizer flags + add_compile_options(-fsanitize=address -fno-omit-frame-pointer -g) + add_link_options(-fsanitize=address) + endif() +else() + message(STATUS "Address sanitizer disabled (enable with -DUSE_ASAN=ON)") +endif() + add_library(aaruformat SHARED include/aaruformat/consts.h include/aaruformat/enums.h diff --git a/README.md b/README.md index f286239..408132e 100644 --- a/README.md +++ b/README.md @@ -42,4 +42,43 @@ Things to be implemented not in the C# version (maybe): - Compile for PlayStation 3 - Snapshots - Parent images -- Data positioning measurements \ No newline at end of file +- Data positioning measurements + +## Building and Testing + +### Standard Build + +```bash +mkdir build +cd build +cmake .. +cmake --build . +``` + +### Running Tests + +```bash +cd build +ctest --verbose +``` + +### Building with Address Sanitizer + +For debugging memory issues, you can build with Address Sanitizer enabled: + +```bash +mkdir build-asan +cd build-asan +cmake -DUSE_ASAN=ON -DCMAKE_BUILD_TYPE=Debug .. +cmake --build . +ctest --verbose +``` + +For detailed information on using Address Sanitizer to detect memory issues, +see [docs/ASAN_USAGE.md](docs/ASAN_USAGE.md). + +### Other Build Options + +- `-DUSE_SLOG=ON` - Enable slog logging for debugging +- `-DUSE_ASAN=ON` - Enable Address Sanitizer for memory error detection + diff --git a/docs/ASAN_USAGE.md b/docs/ASAN_USAGE.md new file mode 100644 index 0000000..45c48d0 --- /dev/null +++ b/docs/ASAN_USAGE.md @@ -0,0 +1,383 @@ +# Address Sanitizer (ASan) Usage Guide + +## Overview + +Address Sanitizer (ASan) is a fast memory error detector that helps identify: +- Buffer overflows (heap, stack, and global) +- Use-after-free bugs +- Use-after-return bugs +- Use-after-scope bugs +- Double-free bugs +- Memory leaks +- Invalid pointer operations + +This guide explains how to build and test libaaruformat with Address Sanitizer enabled. + +## Prerequisites + +### Compiler Support + +Address Sanitizer requires a modern compiler: +- **GCC**: Version 4.8 or later +- **Clang**: Version 3.1 or later +- **MSVC**: Version 16.9 or later (Visual Studio 2019 16.9+) + +### Platform Support + +ASan is supported on: +- Linux (x86_64, ARM, AArch64) +- macOS (x86_64, ARM64) +- Windows (x86_64, ARM64) + +## Building with Address Sanitizer + +### Option 1: Using CMake Command Line + +Build the library and tests with ASan enabled: + +```bash +# Create a separate build directory for ASan builds +mkdir build-asan +cd build-asan + +# Configure with ASan enabled +cmake -DUSE_ASAN=ON .. + +# Build the library and tests +cmake --build . +``` + +### Option 2: Using CMake with Build Type + +For additional debug information (recommended): + +```bash +mkdir build-asan +cd build-asan + +# Configure with ASan and Debug build type +cmake -DUSE_ASAN=ON -DCMAKE_BUILD_TYPE=Debug .. + +# Build +cmake --build . +``` + +### Option 3: Cross-Platform Build Script + +For convenience, use the provided build script: + +```bash +# On Linux/macOS +./build.sh -DUSE_ASAN=ON -DCMAKE_BUILD_TYPE=Debug + +# On Windows (PowerShell) +cmake -DUSE_ASAN=ON -DCMAKE_BUILD_TYPE=Debug -B build-asan -S . +cmake --build build-asan +``` + +## Running Tests with Address Sanitizer + +### Basic Test Execution + +Once built with ASan, run the tests normally: + +```bash +cd build-asan +ctest --verbose +``` + +Or run the test executable directly: + +```bash +cd build-asan/tests +./tests_run +``` + +### Understanding ASan Output + +When ASan detects an error, it will print a detailed report including: + +1. **Error type**: e.g., "heap-buffer-overflow", "use-after-free" +2. **Stack trace**: Shows where the error occurred +3. **Memory allocation context**: Shows where the problematic memory was allocated +4. **Shadow byte legend**: Explains the memory state + +Example ASan error output: +``` +================================================================= +==12345==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000014 at pc 0x000000400d3c bp 0x7fff5fbff7f0 sp 0x7fff5fbff7e8 +READ of size 4 at 0x602000000014 thread T0 + #0 0x400d3b in main example.c:5 + #1 0x7ffff7a05b44 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21b44) + #2 0x400c48 in _start (a.out+0x400c48) + +0x602000000014 is located 0 bytes to the right of 4-byte region [0x602000000010,0x602000000014) +allocated by thread T0 here: + #0 0x7ffff7aaecf8 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.4+0xd9cf8) + #1 0x400d06 in main example.c:4 + #2 0x7ffff7a05b44 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21b44) +================================================================= +``` + +### Environment Variables + +ASan behavior can be customized using the `ASAN_OPTIONS` environment variable: + +#### Detect Memory Leaks + +```bash +# Enable leak detection (default on Linux, not on macOS by default) +export ASAN_OPTIONS=detect_leaks=1 +./tests_run +``` + +#### Continue After First Error + +```bash +# By default, ASan stops after the first error. To continue: +export ASAN_OPTIONS=halt_on_error=0 +./tests_run +``` + +#### Save Error Reports to File + +```bash +# Save ASan reports to files instead of stderr +export ASAN_OPTIONS=log_path=/tmp/asan.log +./tests_run +# Reports will be saved as /tmp/asan.log.12345 (with PID) +``` + +#### Combine Multiple Options + +```bash +export ASAN_OPTIONS=detect_leaks=1:log_path=/tmp/asan.log:halt_on_error=0 +./tests_run +``` + +#### Useful Options Summary + +| Option | Description | Default | +|--------|-------------|---------| +| `detect_leaks=1` | Enable memory leak detection | 1 (Linux), 0 (macOS) | +| `halt_on_error=0` | Continue after first error | 1 | +| `log_path=path` | Save reports to files | stderr | +| `verbosity=1` | Increase verbosity level | 0 | +| `symbolize=1` | Symbolize stack traces | 1 | +| `abort_on_error=1` | Call abort() on error | 0 | +| `print_stats=1` | Print statistics at exit | 0 | + +## Platform-Specific Notes + +### Linux + +ASan works out of the box on most Linux distributions. Make sure you have debug symbols installed for better stack traces: + +```bash +# Ubuntu/Debian +sudo apt-get install binutils + +# Fedora/RHEL +sudo dnf install binutils +``` + +### macOS + +**Important Note**: Leak detection (`detect_leaks=1`) is **not supported** on macOS. If you try to enable it, ASan will abort with an error: `AddressSanitizer: detect_leaks is not supported on this platform.` + +On macOS, use ASan options without leak detection: + +```bash +# Correct for macOS +export ASAN_OPTIONS="print_stats=1:color=always" + +# WRONG - will cause abort on macOS +export ASAN_OPTIONS="detect_leaks=1" # Don't use this! +``` + +For better symbolization on macOS: + +```bash +# Install llvm-symbolizer if using Clang +brew install llvm +export PATH="/usr/local/opt/llvm/bin:$PATH" +``` + +System Integrity Protection (SIP) does not need to be disabled for basic ASan functionality. + +### Windows (MSVC) + +On Windows with MSVC, ASan requires: + +1. Visual Studio 2019 version 16.9 or later +2. The "C++ AddressSanitizer" component installed + +Additional MSVC-specific considerations: + +```cmd +REM Run tests in Command Prompt or PowerShell +cd build-asan\tests +.\tests_run.exe + +REM For better symbols, ensure PDB files are generated +cmake -DUSE_ASAN=ON -DCMAKE_BUILD_TYPE=Debug .. +``` + +## Common Issues and Solutions + +### Issue: False Positives + +**Problem**: ASan reports errors in third-party libraries you can't fix. + +**Solution**: Use suppressions file: + +1. Create a file `asan_suppressions.txt`: + ``` + # Suppress leak in third-party library + leak:libthirdparty.so + ``` + +2. Set the suppressions file: + ```bash + export ASAN_OPTIONS=suppressions=./asan_suppressions.txt + ./tests_run + ``` + +### Issue: Performance Impact + +**Problem**: Tests run significantly slower with ASan. + +**Solution**: This is expected. ASan typically adds 2x-3x overhead. Use ASan builds only for debugging, not production. + +### Issue: Out of Memory + +**Problem**: ASan uses significant additional memory (2x-3x normal). + +**Solution**: +- Run fewer tests in parallel +- Increase system swap space if needed +- Test in smaller batches + +### Issue: Missing Stack Traces + +**Problem**: Stack traces show addresses but no symbols. + +**Solution**: +- Build with debug symbols: `-DCMAKE_BUILD_TYPE=Debug` +- Install `llvm-symbolizer` and ensure it's in PATH +- On Linux: Install debug packages for system libraries + +## Integration with CI/CD + +### GitHub Actions Example + +```yaml +name: ASan Tests + +on: [push, pull_request] + +jobs: + asan: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y cmake build-essential + + - name: Build with ASan + run: | + mkdir build-asan + cd build-asan + cmake -DUSE_ASAN=ON -DCMAKE_BUILD_TYPE=Debug .. + cmake --build . + + - name: Run tests with ASan + run: | + cd build-asan + export ASAN_OPTIONS=detect_leaks=1:halt_on_error=1 + ctest --verbose +``` + +### GitLab CI Example + +```yaml +asan-tests: + stage: test + script: + - mkdir build-asan && cd build-asan + - cmake -DUSE_ASAN=ON -DCMAKE_BUILD_TYPE=Debug .. + - cmake --build . + - export ASAN_OPTIONS=detect_leaks=1:halt_on_error=1 + - ctest --verbose + artifacts: + when: on_failure + paths: + - build-asan/Testing/Temporary/LastTest.log +``` + +## Best Practices + +1. **Regular Testing**: Run ASan builds regularly, not just when debugging +2. **Separate Build Directory**: Always use a separate build directory for ASan builds +3. **Debug Mode**: Combine ASan with Debug build type for maximum information +4. **Clean Environment**: Test with a clean environment to avoid interference +5. **Document Suppressions**: If you use suppressions, document why each one is needed +6. **CI Integration**: Add ASan tests to your CI pipeline for continuous validation + +## Additional Resources + +- [AddressSanitizer Documentation (Google)](https://github.com/google/sanitizers/wiki/AddressSanitizer) +- [Clang AddressSanitizer](https://clang.llvm.org/docs/AddressSanitizer.html) +- [GCC Instrumentation Options](https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html) +- [MSVC AddressSanitizer](https://docs.microsoft.com/en-us/cpp/sanitizers/asan) + +## Quick Reference + +### Build Commands + +```bash +# Standard ASan build +cmake -DUSE_ASAN=ON -B build-asan -S . +cmake --build build-asan + +# ASan build with debug symbols +cmake -DUSE_ASAN=ON -DCMAKE_BUILD_TYPE=Debug -B build-asan -S . +cmake --build build-asan + +# Run tests +cd build-asan && ctest --verbose +``` + +### Environment Variables + +```bash +# Enable all leak detection +export ASAN_OPTIONS=detect_leaks=1 + +# Save reports to files +export ASAN_OPTIONS=log_path=/tmp/asan.log + +# Multiple options +export ASAN_OPTIONS=detect_leaks=1:log_path=/tmp/asan.log:halt_on_error=0 +``` + +## Troubleshooting + +If you encounter issues: + +1. Check compiler version supports ASan +2. Verify CMake found the correct compiler +3. Look for conflicting compiler flags +4. Check environment variables aren't interfering +5. Try with a minimal test case first +6. Review ASan documentation for your specific platform + +For project-specific issues, please file a bug report with: +- Platform and compiler version +- Full CMake configuration output +- Complete ASan error report +- Steps to reproduce + diff --git a/run-asan-tests.ps1 b/run-asan-tests.ps1 new file mode 100644 index 0000000..c4e9f95 --- /dev/null +++ b/run-asan-tests.ps1 @@ -0,0 +1,89 @@ +# Quick script to build and run tests with Address Sanitizer on Windows +# Usage: .\run-asan-tests.ps1 [asan_options] +# +# Example: +# .\run-asan-tests.ps1 # Run with default ASan options +# .\run-asan-tests.ps1 "detect_leaks=1" # Enable leak detection +# .\run-asan-tests.ps1 "detect_leaks=1:halt_on_error=0" # Multiple options + +param( + [string]$AsanOptions = "" +) + +$ErrorActionPreference = "Stop" + +# Build directory for ASan +$BuildDir = "build-asan" + +Write-Host "========================================" -ForegroundColor Blue +Write-Host "Address Sanitizer Test Runner" -ForegroundColor Blue +Write-Host "========================================" -ForegroundColor Blue +Write-Host "" + +# Check if build directory exists and is configured +if (-not (Test-Path $BuildDir) -or -not (Test-Path "$BuildDir/CMakeCache.txt")) { + Write-Host "Build directory '$BuildDir' not configured. Setting up..." -ForegroundColor Yellow + New-Item -ItemType Directory -Path $BuildDir -Force | Out-Null + + Write-Host "Running CMake configuration..." -ForegroundColor Blue + try { + cmake -DUSE_ASAN=ON -DCMAKE_BUILD_TYPE=Debug -B $BuildDir -S . + } + catch { + Write-Host "CMake configuration failed!" -ForegroundColor Red + exit 1 + } +} + +# Build the project +Write-Host "Building with Address Sanitizer..." -ForegroundColor Blue +try { + cmake --build $BuildDir --config Debug +} +catch { + Write-Host "Build failed!" -ForegroundColor Red + exit 1 +} + +Write-Host "Build completed successfully!" -ForegroundColor Green +Write-Host "" + +# Set up ASan options +if ($AsanOptions -ne "") { + $env:ASAN_OPTIONS = $AsanOptions + Write-Host "Using custom ASAN_OPTIONS: $env:ASAN_OPTIONS" -ForegroundColor Yellow +} +else { + # Default options: detect leaks (if supported), print stats + $env:ASAN_OPTIONS = "print_stats=1" + Write-Host "Using default ASAN_OPTIONS: $env:ASAN_OPTIONS" -ForegroundColor Yellow +} + +Write-Host "" +Write-Host "Running tests..." -ForegroundColor Blue +Write-Host "========================================" -ForegroundColor Blue +Write-Host "" + +# Run tests +Push-Location "$BuildDir/tests" +try { + ctest --verbose --output-on-failure -C Debug + Write-Host "" + Write-Host "========================================" -ForegroundColor Green + Write-Host "All tests passed!" -ForegroundColor Green + Write-Host "========================================" -ForegroundColor Green + Pop-Location + exit 0 +} +catch { + Write-Host "" + Write-Host "========================================" -ForegroundColor Red + Write-Host "Tests failed!" -ForegroundColor Red + Write-Host "========================================" -ForegroundColor Red + Write-Host "" + Write-Host "Check the output above for Address Sanitizer reports." -ForegroundColor Yellow + Write-Host "See docs\ASAN_USAGE.md for help interpreting the results." -ForegroundColor Yellow + Pop-Location + exit 1 +} + diff --git a/run-asan-tests.sh b/run-asan-tests.sh new file mode 100755 index 0000000..edf9181 --- /dev/null +++ b/run-asan-tests.sh @@ -0,0 +1,90 @@ +#!/bin/bash + +# Quick script to build and run tests with Address Sanitizer +# Usage: ./run-asan-tests.sh [asan_options] +# +# Example: +# ./run-asan-tests.sh # Run with default ASan options +# ./run-asan-tests.sh detect_leaks=1 # Enable leak detection +# ./run-asan-tests.sh "detect_leaks=1:halt_on_error=0" # Multiple options + +set -e # Exit on error + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Build directory for ASan +BUILD_DIR="build-asan" + +echo -e "${BLUE}========================================${NC}" +echo -e "${BLUE}Address Sanitizer Test Runner${NC}" +echo -e "${BLUE}========================================${NC}" +echo "" + +# Check if build directory exists and is configured +if [ ! -d "$BUILD_DIR" ] || [ ! -f "$BUILD_DIR/CMakeCache.txt" ]; then + echo -e "${YELLOW}Build directory '$BUILD_DIR' not configured. Setting up...${NC}" + mkdir -p "$BUILD_DIR" + + echo -e "${BLUE}Running CMake configuration...${NC}" + cmake -DUSE_ASAN=ON -DCMAKE_BUILD_TYPE=Debug -B "$BUILD_DIR" -S . || { + echo -e "${RED}CMake configuration failed!${NC}" + exit 1 + } +fi + +# Build the project +echo -e "${BLUE}Building with Address Sanitizer...${NC}" +cmake --build "$BUILD_DIR" -j$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4) || { + echo -e "${RED}Build failed!${NC}" + exit 1 +} + +echo -e "${GREEN}Build completed successfully!${NC}" +echo "" + +# Set up ASan options +if [ -n "$1" ]; then + export ASAN_OPTIONS="$1" + echo -e "${YELLOW}Using custom ASAN_OPTIONS: $ASAN_OPTIONS${NC}" +else + # Default options: print stats, use colors + # Note: detect_leaks is not supported on macOS, so we don't enable it by default + if [[ "$OSTYPE" == "darwin"* ]]; then + export ASAN_OPTIONS="print_stats=1:color=always" + echo -e "${YELLOW}Using default ASAN_OPTIONS (macOS): $ASAN_OPTIONS${NC}" + echo -e "${YELLOW}Note: leak detection not supported on macOS${NC}" + else + export ASAN_OPTIONS="detect_leaks=1:print_stats=1:color=always" + echo -e "${YELLOW}Using default ASAN_OPTIONS: $ASAN_OPTIONS${NC}" + fi +fi + +echo "" +echo -e "${BLUE}Running tests...${NC}" +echo -e "${BLUE}========================================${NC}" +echo "" + +# Run tests +cd "$BUILD_DIR/tests" +if ctest --verbose --output-on-failure; then + echo "" + echo -e "${GREEN}========================================${NC}" + echo -e "${GREEN}All tests passed!${NC}" + echo -e "${GREEN}========================================${NC}" + exit 0 +else + echo "" + echo -e "${RED}========================================${NC}" + echo -e "${RED}Tests failed!${NC}" + echo -e "${RED}========================================${NC}" + echo "" + echo -e "${YELLOW}Check the output above for Address Sanitizer reports.${NC}" + echo -e "${YELLOW}See docs/ASAN_USAGE.md for help interpreting the results.${NC}" + exit 1 +fi + diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 68b7627..ac3bf6a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -102,10 +102,42 @@ target_include_directories(tests_run # Link libraries target_link_libraries(tests_run PRIVATE gtest gtest_main aaruformat) +# Apply address sanitizer flags to tests if enabled +if(USE_ASAN) + if("${CMAKE_C_COMPILER_ID}" MATCHES "MSVC") + # MSVC address sanitizer flags for tests + target_compile_options(tests_run PRIVATE /fsanitize=address) + target_link_options(tests_run PRIVATE /fsanitize=address) + else() + # GCC/Clang address sanitizer flags for tests + target_compile_options(tests_run PRIVATE -fsanitize=address -fno-omit-frame-pointer -g) + target_link_options(tests_run PRIVATE -fsanitize=address) + endif() + message(STATUS "Address sanitizer enabled for tests") +endif() + # Ensure test data is copied before running tests add_dependencies(tests_run copy_test_data) # Integrate with CTest (per-test reporting) enable_testing() include(GoogleTest) -gtest_discover_tests(tests_run) + +# Configure test discovery with proper ASAN_OPTIONS +if(USE_ASAN) + # On macOS, leak detection is not supported and causes abort + # Set ASAN_OPTIONS for test discovery to avoid this + if(APPLE) + set(DISCOVERY_ASAN_OPTIONS "print_stats=0") + else() + set(DISCOVERY_ASAN_OPTIONS "detect_leaks=0:print_stats=0") + endif() + + gtest_discover_tests(tests_run + DISCOVERY_TIMEOUT 30 + PROPERTIES ENVIRONMENT "ASAN_OPTIONS=${DISCOVERY_ASAN_OPTIONS}" + ) +else() + gtest_discover_tests(tests_run) +endif() +