Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/TinyCC/tinycc/llms.txt

Use this file to discover all available pages before exploring further.

TinyCC provides multiple debugging capabilities including debug symbols, runtime backtraces, and integration with standard debuggers.

Debug information formats

TinyCC supports two debug information formats:
  • STABS - Default debug format (traditional Unix format)
  • DWARF - Modern debug format (versions 2-5)

Generating debug symbols

# Compile with STABS debug information (default)
tcc -g program.c -o program

# Run with debug info
./program
DWARF debug information provides better compatibility with modern debuggers like GDB and LLDB.

Runtime backtraces

TinyCC can generate stack backtraces at runtime without external tools.

Enabling backtraces

1

Compile with backtrace support

Use the -bt option to enable runtime stack traces:
# Enable backtraces with default depth (6 frames)
tcc -g -bt program.c -o program

# Enable backtraces with custom depth (N frames)
tcc -g -bt[N] program.c -o program
Example with custom depth:
# Show up to 10 callers in stack traces
tcc -g -bt10 program.c -o program
2

Run the program

When a runtime error occurs, TCC automatically prints a stack trace:
./program
Output example:
test.c:68: in function 'test5()': dereferencing invalid pointer
test.c:75: by main

Backtrace API

When compiled with -bt, the __TCC_BACKTRACE__ macro is defined and you can trigger backtraces programmatically:
#ifdef __TCC_BACKTRACE__
#include <stdio.h>

// Function prototype provided by TCC
int tcc_backtrace(const char *fmt, ...);

int main() {
    // Trigger a backtrace on demand
    tcc_backtrace("Debug checkpoint reached\n");
    
    // Conditional backtrace
    if (error_condition) {
        tcc_backtrace("Error detected: %s\n", error_msg);
    }
    
    return 0;
}
#endif

Using standard debuggers

GDB integration

TinyCC-generated executables can be debugged with GDB:
1

Compile with debug symbols

tcc -g -o program program.c
2

Run GDB

gdb ./program
3

Debug commands

Common GDB commands:
# Set breakpoint
(gdb) break main
(gdb) break program.c:42

# Run program
(gdb) run [args]

# Step through code
(gdb) next      # Step over
(gdb) step      # Step into
(gdb) continue  # Continue execution

# Examine variables
(gdb) print variable_name
(gdb) info locals

# View backtrace
(gdb) backtrace
(gdb) bt

DWARF with GDB

For better GDB compatibility, use DWARF debug format:
# Compile with DWARF 4
tcc -gdwarf-4 program.c -o program

# Configure TinyCC to use DWARF by default
./configure --config-dwarf=4
make
DWARF 4 and 5 provide the best compatibility with modern versions of GDB (7.0+).

Debug-only code

Use preprocessor macros to include debug-only code:
#include <stdio.h>

#ifdef DEBUG
#define DEBUG_PRINT(fmt, ...) \
    fprintf(stderr, "DEBUG: " fmt, ##__VA_ARGS__)
#else
#define DEBUG_PRINT(fmt, ...) /* nothing */
#endif

int main() {
    int value = 42;
    
    DEBUG_PRINT("Value is: %d\n", value);
    
    return 0;
}
Compile with debug macros:
# Enable debug output
tcc -DDEBUG -g program.c -o program

# Production build (no debug output)
tcc program.c -o program

Bounds checking

TinyCC includes optional runtime bounds checking to detect memory errors.

Enabling bounds checking

# Compile with bounds checking
tcc -b program.c -o program

# Bounds checking implies -g
tcc -b program.c -o program
Bounds checking is available on i386, x86_64, ARM, ARM64, and RISC-V 64.

Types of errors detected

Bounds checking catches:
// Out of bounds array access
int tab[10];
for (int i = 0; i < 11; i++) {
    sum += tab[i];  // Error on i=10
}

Bounds checking environment variables

Control bounds checking behavior:
# Print warning when pointer add creates illegal pointer
export TCC_BOUNDS_WARN_POINTER_ADD=1

# Print bound checking calls (for debugging)
export TCC_BOUNDS_PRINT_CALLS=1

# Print heap objects not freed at exit
export TCC_BOUNDS_PRINT_HEAP=1

# Print statistics at exit
export TCC_BOUNDS_PRINT_STATISTIC=1

# Continue execution after bound error (unsafe)
export TCC_BOUNDS_NEVER_FATAL=1

# Run program with bounds checking
./program

Disabling bounds checking in code

Temporarily disable bounds checking for specific code sections:
#ifdef __TCC_BCHECK__
extern void __bounds_checking(int x);
#define BOUNDS_CHECKING_OFF __bounds_checking(1)
#define BOUNDS_CHECKING_ON  __bounds_checking(-1)
#else
#define BOUNDS_CHECKING_OFF
#define BOUNDS_CHECKING_ON
#endif

void performance_critical_function() {
    BOUNDS_CHECKING_OFF;
    
    // Fast code without bounds checks
    for (int i = 0; i < 1000000; i++) {
        // ... tight loop ...
    }
    
    BOUNDS_CHECKING_ON;
}
Disabling bounds checking should only be done in performance-critical sections where memory safety is guaranteed.

Symbol information

TinyCC preserves symbol information for debugging and dynamic linking.

Viewing symbols

# View symbols in executable
nm program

# View only defined symbols
nm -g program

# Use readelf for detailed symbol table
readelf -s program

# Use objdump for symbol information
objdump -t program

Exporting symbols

# Export all global symbols (useful for dlopen)
tcc -rdynamic program.c -o program

# Equivalent linker option
tcc -Wl,--export-dynamic program.c -o program

Debug information in shared libraries

Compile shared libraries with debug symbols:
# Create shared library with debug info
tcc -shared -g -o libmylib.so mylib.c

# Use library with debug symbols
tcc -g program.c -L. -lmylib -o program

# Debug with GDB
gdb ./program
Bounds checking code is not included in shared libraries. The main executable must be compiled with -b for bounds checking.

Runtime debugging with -run

Debug code directly with -run:
# Run with backtraces enabled
tcc -g -bt -run program.c arg1 arg2

# Run with bounds checking
tcc -b -run program.c

# Combine options
tcc -g -bt -b -run program.c
Errors are reported with full context:
program.c:15: in function 'process_data': invalid memory access
program.c:42: by main

Profiling support

Create a profiling version of TCC:
# Build profiling version
make tcc_p

# Use for profiling
./tcc_p program.c -o program

Limitations

Debug limitations:
  • Signal handlers are not compatible with bounds checking
  • Fork in multi-threaded applications may cause issues with bounds checking
  • Generated code with -b is slower and larger
  • Some optimizations are disabled with debug symbols

Configuration

Build-time debug configuration

Configure debug features during TCC compilation:
# Disable backtrace support
./configure --config-backtrace=no

# Disable bounds checking
./configure --config-bcheck=no

# Set DWARF version (2-5)
./configure --config-dwarf=4

# Debug TCC itself
./configure --debug

See also

Build docs developers (and LLMs) love