Fundamentals 24 min read

Debugging Memory Out-of-Bounds Errors in Linux: Techniques, Tools, and Best Practices

This article explains what memory out-of-bounds errors are, why they are dangerous in Linux C programs, and provides step‑by‑step debugging methods using logs, GDB, Valgrind, core dumps, mprotect, static analysis, and preventive coding practices.

Deepin Linux
Deepin Linux
Deepin Linux
Debugging Memory Out-of-Bounds Errors in Linux: Techniques, Tools, and Best Practices

As a developer, you may have experienced a program that crashes or behaves erratically on Linux due to hidden memory out‑of‑bounds bugs.

1. Overview of Memory Out‑of‑Bounds

In Linux, programs rely on proper memory allocation; when they access memory outside their allocated region, they can corrupt data, cause leaks, or crash the entire process. Large projects make such bugs hard to locate, yet they can lead to severe economic and user‑experience losses.

2. Understanding Memory Out‑of‑Bounds: Symptoms and Causes

2.1 Symptoms

Typical symptoms include program crashes, corrupted data (e.g., wrong student grades), and performance degradation due to repeated illegal memory accesses.

Example of a simple C program that overruns a 5‑element array:

#include
#include
int main() {
    int *array = (int*)malloc(5 * sizeof(int));
    for(int i = 0; i < 10; i++) {
        array[i] = i;
    }
    for(int i = 0; i < 10; i++) {
        printf("array[%d] = %d\n", i, array[i]);
    }
    free(array);
    return 0;
}

2.2 Causes

Common causes are array index overflow, improper pointer usage, and misuse of dynamic memory allocation.

Array overflow example:

#include
int main() {
    int numbers[5] = {1, 2, 3, 4, 5};
    for (int i = 0; i <= 5; i++) {
        printf("%d ", numbers[i]);
    }
    return 0;
}

Invalid pointer dereference example:

#include
#include
int main() {
    int *ptr = NULL;
    *ptr = 10;  // dereferencing a null pointer
    return 0;
}

Use‑after‑free example:

#include
#include
int main() {
    int *ptr = (int*)malloc(5 * sizeof(int));
    free(ptr);
    *ptr = 10;  // accessing freed memory
    return 0;
}

3. Locating Memory Out‑of‑Bounds: Step‑by‑Step

3.1 Basic Investigation: Logs and Debug Output

Insert logging with printf or syslog to capture runtime information. Example:

#include
#include
#include
int main() {
    FILE *file = fopen("test.txt", "r");
    if (file == NULL) {
        syslog(LOG_ERR, "Failed to open file test.txt");
        return 1;
    }
    // other file handling
    fclose(file);
    return 0;
}

Use GDB to set breakpoints, step through code, and inspect variables.

3.2 Advanced Tool: Valgrind

Install Valgrind (e.g., sudo apt-get install valgrind ) and run with valgrind --tool=memcheck --leak-check=yes ./test to detect invalid reads/writes, leaks, and out‑of‑bounds accesses.

#include
#include
int main() {
    int *array = (int*)malloc(5 * sizeof(int));
    for(int i = 0; i < 10; i++) {
        array[i] = i;  // intentional overflow
    }
    free(array);
    return 0;
}

Valgrind output will pinpoint the exact line and address of the illegal write.

3.3 Memory Protection with mprotect

The mprotect function can change page protection to catch out‑of‑bounds writes.

#include
#include
int mprotect(const void *start, size_t len, int prot);

Example that marks a page read‑only and then triggers a segmentation fault when the program writes beyond the array:

#include
#include
#include
#include
#include
#include
#include
#define ARRAY_SIZE 10
#define PAGE_SIZE 4096

int main() {
    int *array = (int*)malloc(ARRAY_SIZE * sizeof(int));
    if (array == NULL) {
        perror("malloc");
        return 1;
    }
    void *page_start = (void*)((unsigned long)array & ~(PAGE_SIZE - 1));
    if (mprotect(page_start, PAGE_SIZE, PROT_READ) == -1) {
        perror("mprotect");
        free(array);
        return 1;
    }
    for(int i = 0; i < ARRAY_SIZE + 5; i++) {
        array[i] = i; // out‑of‑bounds write triggers SIGSEGV
    }
    free(array);
    return 0;
}

4. Analyzing Memory Out‑of‑Bounds: Core Dumps and Static Analysis

4.1 Core Dump Files: Unlocking Critical Information

Enable core dumps with ulimit -c unlimited , set a pattern via /proc/sys/kernel/core_pattern , then use GDB to load the core file ( gdb ./test core.1234 ) and inspect backtraces ( bt ), registers, and variables.

4.2 Static Analysis: Code Review Techniques

Manually review array bounds, pointer initialization, and proper free usage. Tools like cppcheck or pclint can automate detection of potential out‑of‑bounds errors.

5. Fixing Memory Out‑of‑Bounds: Remedies

5.1 Code Corrections

Adjust array sizes, ensure pointers are allocated before use, and set pointers to NULL after freeing.

Corrected array example:

#include
int main() {
    int numbers[6] = {1, 2, 3, 4, 5, 6};
    for (int i = 0; i <= 5; i++) {
        printf("%d ", numbers[i]);
    }
    return 0;
}

Proper pointer allocation example:

#include
#include
int main() {
    int *ptr = (int*)malloc(sizeof(int));
    if (ptr != NULL) {
        *ptr = 10;
        free(ptr);
    }
    return 0;
}

Set pointer to NULL after free to avoid use‑after‑free:

#include
#include
int main() {
    int *ptr = (int*)malloc(5 * sizeof(int));
    if (ptr != NULL) {
        free(ptr);
        ptr = NULL;
    }
    return 0;
}

5.2 Prevention: Best Practices

Use safe library functions (e.g., strncpy , snprintf ) instead of unsafe ones like strcpy . Perform explicit bounds checking before array or pointer access, follow clear coding standards, and document code thoroughly.

#include
#include
int main() {
    char dest[10];
    char src[] = "Hello, World!";
    strncpy(dest, src, sizeof(dest) - 1);
    dest[sizeof(dest) - 1] = '\0';
    printf("%s\n", dest);
    return 0;
}

Encapsulate allocation and deallocation in dedicated functions with comments to improve readability and maintainability.

#include
#include
// Allocate and initialize
int* allocate_and_initialize(int size) {
    int *ptr = (int*)malloc(size * sizeof(int));
    if (ptr != NULL) {
        for (int i = 0; i < size; i++) {
            ptr[i] = i;
        }
    }
    return ptr;
}

// Free memory
void free_memory(int *ptr) {
    if (ptr != NULL) {
        free(ptr);
    }
}

int main() {
    int *array = allocate_and_initialize(5);
    if (array != NULL) {
        for (int i = 0; i < 5; i++) {
            printf("%d ", array[i]);
        }
        free_memory(array);
    }
    return 0;
}

By following these practices, developers can significantly reduce the risk of memory out‑of‑bounds bugs in Linux C applications.

LinuxC programmingGDBMemory DebuggingValgrindmprotectout-of-bounds
Deepin Linux
Written by

Deepin Linux

Research areas: Windows & Linux platforms, C/C++ backend development, embedded systems and Linux kernel, etc.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.