Overview
The Debugging Utilities module provides comprehensive debugging and diagnostic tools for ascii-chat development and troubleshooting. This module includes error tracking, lock debugging, memory leak detection, and diagnostic utilities to help identify and resolve issues in the codebase.
Key Features:
- Thread-local error context with stack traces
- Lock debugging and deadlock detection
- Memory leak detection and tracking
- Safe memory allocation macros with error checking
- Debug build utilities for diagnostics
Debug Modules
Error Handling (asciichat_errno)
The error handling system provides thread-local error tracking with full context:
Thread-Local Error Context:
- Each thread has independent error state
- Automatic file/line/function capture
- Stack trace capture in debug builds
- System error integration (errno, WSA errors)
Error Context Structure:
typedef struct {
const char *file;
int line;
const char *function;
char *stack_trace;
char *context_message;
time_t timestamp;
int system_error;
asciichat_error_t error_code
asciichat_error_t
Error and exit codes - unified status values (0-255)
Library Macros:
#define SET_ERRNO_SYS(code, context_msg,...)
Set error code with custom message and system error context.
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
Application Macros:
log_error(
"Library error: %s at %s:%d in %s()",
}
#define GET_ERRNO()
Get current error code (0 if no error)
#define HAS_ERRNO(var)
Check if an error occurred and get full context.
#define CLEAR_ERRNO()
Clear the current error state.
#define log_error(...)
Log an ERROR message.
const char * function
Function name where error occurred (NULL in release builds)
char * context_message
Optional custom message (dynamically allocated, owned by system)
const char * file
Source file where error occurred (NULL in release builds)
int line
Line number where error occurred (0 in release builds)
Debug Features:
- Stack traces captured automatically in debug builds
- Full error context in log messages
- Error statistics tracking per thread
- Integration with FATAL() macro
Lock Debugging (lock_debug)
The lock debugging system tracks all mutex and rwlock acquisitions to help identify deadlocks and lock contention issues:
Lock Tracking:
- Tracks all mutex and rwlock acquisitions with backtraces
- Thread-safe lock record management
- Automatic cleanup of lock records on unlock
- Integration with platform backtrace functionality
Lock Record Structure:
typedef struct lock_record {
void *lock_address;
lock_type_t lock_type;
struct timespec acquisition_time;
const char *file_name;
int line_number;
const char *function_name;
void *backtrace_buffer[32];
char **backtrace_symbols;
} lock_record_t;
unsigned long long uint64_t
Usage:
lock_debug_init();
DEBUG_MUTEX_LOCK(&my_mutex);
DEBUG_MUTEX_UNLOCK(&my_mutex);
lock_debug_start_thread();
lock_debug_print_state();
Debug Thread:
- Runs in background monitoring lock state
- Press '?' key to trigger lock information printing
- Prints all held locks with full backtraces
- Helps identify deadlocks and lock contention
Lock Statistics:
- Total locks acquired/released (lifetime)
- Current number of held locks
- Lock usage statistics by code location
- Orphaned release detection (unlocks without locks)
Memory Debugging (common.h)
The memory debugging system provides comprehensive memory leak detection and tracking:
Memory Macros:
- SAFE_MALLOC: Safe malloc with error checking
- SAFE_CALLOC: Safe calloc with error checking
- SAFE_REALLOC: Safe realloc with error checking
- SAFE_FREE: Safe free that sets pointer to NULL
- SAFE_MALLOC_ALIGNED: Aligned memory allocation
- SAFE_MALLOC_SIMD: SIMD-aligned memory allocation
Usage:
#define SAFE_REALLOC(ptr, size, cast)
#define SAFE_MALLOC(size, cast)
#define SAFE_CALLOC(count, size, cast)
Debug Memory Tracking:
When DEBUG_MEMORY is defined and CMAKE_BUILD_TYPE=Debug:
Leak Detection:
- All allocations tracked with address, size, file, line, timestamp
- On program exit, reports any unfreed allocations
- Thread-safe allocation tracking
- Comprehensive leak reports
Allocation Logging:
[MEMORY] malloc(1024) = 0x7f1234567890 at src/client.c:123 in process_frame()
[MEMORY] free(0x7f1234567890) at src/client.c:456 in cleanup_frame()
Double-Free Detection:
- Detects freeing already-freed memory
- Triggers assertion with full context
- Prevents use-after-free bugs
Memory Reports:
Memory Statistics:
- Current allocations and total size
- Peak allocations and peak size
- Total allocation/deallocation calls
- Leak summary (unfreed allocations)
Integration
Build Configuration
Debug Build:
cmake -DCMAKE_BUILD_TYPE=Debug -DDEBUG_MEMORY=ON ..
Enable Debug Features:
#define DEBUG_MEMORY 1
lock_debug_init();
lock_debug_start_thread();
Runtime Usage
Error Handling:
if (connect_failed) {
}
}
}
Lock Debugging:
DEBUG_MUTEX_LOCK(&my_mutex);
DEBUG_MUTEX_UNLOCK(&my_mutex);
lock_debug_print_state();
Memory Debugging:
debug_memory_report();
debug_memory_set_quiet_mode(true);
Best Practices
- Use Error Handling Consistently:
- Enable Lock Debugging During Development:
lock_debug_init();
lock_debug_start_thread();
DEBUG_MUTEX_LOCK(&mutex);
- Use SAFE_* Macros for All Allocations:
- Check Memory Leaks Regularly:
- Debug Thread for Lock Monitoring:
lock_debug_start_thread();
Performance Impact
Debug Builds:
- Stack trace capture adds ~5-10ms per error
- Lock tracking adds ~1-2ยตs per lock operation
- Memory tracking adds ~1-2ยตs per allocation
- Debug thread uses minimal CPU (waits for '?' key)
Release Builds:
- Error handling: Minimal overhead (thread-local storage)
- Lock debugging: Disabled (no overhead)
- Memory debugging: Disabled (no overhead)
- All debug features are compiled out
Recommendations:
- Use debug builds during development
- Disable debug features in production builds
- Enable memory debugging only when investigating leaks
- Enable lock debugging only when investigating deadlocks
Examples
Error Handling Example
if (sock == INVALID_SOCKET) {
}
if (connect_failed) {
}
}
if (ctx->stack_trace) {
log_debug(
"Stack trace:\n%s", ctx->stack_trace);
}
}
return err;
}
return 0;
}
int main(int argc, char **argv)
#define log_debug(...)
Log a DEBUG message.
Lock Debugging Example
lock_debug_init();
lock_debug_start_thread();
void critical_section() {
DEBUG_MUTEX_LOCK(&g_shared_mutex);
DEBUG_MUTEX_UNLOCK(&g_shared_mutex);
}
lock_debug_cleanup();
}
Memory Debugging Example
#define DEBUG_MEMORY 1
void process_data() {
process(data, buffer);
}
Debugging Tools
These external tools help with debugging and crash analysis. They are not build tools and are not required to compile ascii-chat, but are essential for development.
Stack Trace Symbolizers
Symbolizers convert raw memory addresses into human-readable function names and source locations. ascii-chat's backtrace system (lib/platform/symbols.c) automatically detects and uses these tools when available.
| Tool | Platform | Priority |
| llvm-symbolizer | All | Preferred - best for Clang-compiled binaries |
| addr2line | Linux/macOS | Fallback - GNU binutils alternative |
- Note
- Important: Ensure at least one symbolizer is in your PATH for readable backtraces during debugging. Without a symbolizer, you'll only see raw memory addresses like
0x401234 instead of main() at src/client.c:42.
Automatic Detection (lib/platform/symbols.c):
The symbol cache system automatically:
- Checks for
llvm-symbolizer in PATH (preferred)
- Falls back to
addr2line if llvm-symbolizer is not found
- Uses raw addresses if neither tool is available
- Caches resolved symbols in a hashtable to avoid repeated subprocess calls
You can override the tool paths with environment variables:
LLVM_SYMBOLIZER_PATH - Full path to llvm-symbolizer binary
ADDR2LINE_PATH - Full path to addr2line binary
Verify Symbolizer Availability:
# Check if llvm-symbolizer is available (preferred)
which llvm-symbolizer || echo "llvm-symbolizer not found"
# Check if addr2line is available (fallback)
which addr2line || echo "addr2line not found"
# Windows (PowerShell)
Get-Command llvm-symbolizer -ErrorAction SilentlyContinue
Get-Command addr2line -ErrorAction SilentlyContinue
Manual Symbolization Examples:
# Manual symbolization of an address
llvm-symbolizer --obj=build/bin/ascii-chat 0x401234
# Using addr2line
addr2line -e build/bin/ascii-chat -f 0x401234
# Filter through symbolizer for readable crash output
./build/bin/ascii-chat 2>&1 | llvm-symbolizer
Installation:
# Install on Debian/Ubuntu
sudo apt install llvm binutils
# Install on Arch
sudo pacman -S llvm binutils
# Install on macOS (via Homebrew)
brew install llvm binutils
# Install on Windows (via scoop or LLVM installer)
scoop install llvm
Interactive Debuggers
| Tool | Platform | Description |
| lldb | All | LLVM debugger (recommended for Clang builds) |
| gdb | Linux | GNU debugger |
LLDB Usage:
# Start debugging
lldb ./build/bin/ascii-chat
# Run with arguments
(lldb) run server --port 8080
# Set breakpoint
(lldb) breakpoint set --name main
(lldb) b network_connect
# Examine backtrace on crash
(lldb) bt
# Examine variables
(lldb) frame variable
(lldb) p my_variable
# Continue execution
(lldb) continue
GDB Usage (Linux):
# Start debugging
gdb ./build/bin/ascii-chat
# Run with arguments
(gdb) run server --port 8080
# Set breakpoint
(gdb) break main
# Backtrace
(gdb) bt
Memory Checking Tools
| Tool | Platform | Description |
| valgrind | Linux | Memory error detector (slower but thorough) |
| leaks | macOS | Apple's memory leak checker |
| ASan | All | AddressSanitizer (built into Debug builds) |
Valgrind Usage (Linux):
# Check for memory leaks
valgrind --leak-check=full ./build/bin/ascii-chat server
# Check for invalid memory access
valgrind --tool=memcheck ./build/bin/ascii-chat server
Leaks Usage (macOS):
# Check for leaks at exit
leaks --atExit -- ./build/bin/ascii-chat server
- Note
- Debug builds (
CMAKE_BUILD_TYPE=Debug) automatically enable AddressSanitizer and UndefinedBehaviorSanitizer, which catch most memory errors at runtime.
Binary Analysis Tools
| Tool | Platform | Description |
| ldd | Linux | List dynamic dependencies |
| otool | macOS | List dynamic dependencies |
| objdump | Linux | Disassemble binary |
| llvm-objdump | All | LLVM disassembler |
Verifying Static Linking (Release builds):
# Linux - should say "not a dynamic executable" for static builds
ldd build/bin/ascii-chat
# macOS - should only show system libraries
otool -L build/bin/ascii-chat
- Note
- The build system automatically verifies static linking in Release builds using
check_static_linking.sh - see Build System.
Troubleshooting
Common Issues:
- Stack Traces Not Appearing:
- Ensure CMAKE_BUILD_TYPE=Debug
- Check that debug symbols are available
- Verify addr2line or llvm-symbolizer is in PATH
- Lock Debugging Not Working:
- Ensure lock_debug_init() is called before any locks
- Use DEBUG_* macros instead of regular lock functions
- Check that debug thread is started: lock_debug_start_thread()
- Memory Leaks Not Detected:
- Ensure DEBUG_MEMORY is defined
- Check that CMAKE_BUILD_TYPE=Debug
- Verify mimalloc override is disabled (MI_MALLOC_OVERRIDE not set)
- Use SAFE_* macros for all allocations
- Performance Issues in Debug Builds:
- Stack trace capture adds overhead
- Lock tracking adds small overhead per operation
- Memory tracking adds small overhead per allocation
- Use release builds for performance testing
- Note
- Debug utilities are designed for development and troubleshooting. Disable debug features in production builds for optimal performance.
- See also
- lib/asciichat_errno.h for Errors handling documentation
-
lib/lock_debug.h for lock debugging documentation
-
lib/common.h for memory debugging macros