Introduction #
Memory bugs in C are notoriously tricky. Locating the issue is often much harder than fixing it. One of the most common and dangerous problems is memory overwrite—when code unintentionally writes beyond its allocated region, corrupting adjacent memory.
Such overwrites may cause anything from subtle functional errors to full program crashes. In embedded systems, these bugs can be catastrophic.
Let’s explore how overwrites can occur in static and dynamic storage areas, and techniques to detect them.
Static Memory Overwrite #
Example: Array Out-of-Bounds #
In Linux, a process can typically open up to 1024 file descriptors (0–1023). In one project, we noticed a UART file descriptor (fd
) suddenly holding an invalid value. Debugging revealed that it had been overwritten due to an array out-of-bounds bug:
float arr[5];
int count = 8;
for (size_t i = 0; i < count; i++) {
arr[i] = xxx;
}
Since count
exceeds the array size, memory beyond arr
was overwritten, corrupting other variables, including fd
.
Locating the Bug with a Map File #
In Linux, simply logging values won’t reveal which adjacent variable is being corrupted. A more systematic approach is to generate a map file to see variable placements:
set(CMAKE_EXE_LINKER_FLAGS "-Wl,-Map=output.map") # generate map file
set(CMAKE_C_FLAGS "-fdata-sections") # output static variable addresses
set(CMAKE_CXX_FLAGS "-fdata-sections")
This allows you to trace which variables are next to each other in memory.
Dynamic Memory Overwrite #
Dynamic memory overwrites are equally common. A classic case is improper use of malloc
and strcpy
:
char *str = "hello";
int str_len = strlen(str); // str_len = 5
char *ptr = (char *)malloc(str_len); // only 5 bytes allocated
char *p_a = ptr + 5;
*p_a = 20; // writing beyond allocated space
strcpy(ptr, str); // overwrites adjacent memory
Output shows *p_a
being unexpectedly modified after strcpy
—a silent corruption caused by ignoring the null terminator (\0
), which requires 1 extra byte.
Detecting Memory Overwrites #
When overwrites occur, they may or may not cause immediate crashes, depending on what data is stored nearby. This makes them difficult to reproduce.
Tools You Can Use #
- dmalloc
- valgrind
These runtime tools can help detect illegal memory accesses.
Custom Detection with “Red Zones” #
Another technique is to implement red zones—guard regions around allocated memory.
- Before Red Zone: 4 bytes, fixed value
0x11223344
- After Red Zone: 4 bytes, fixed value
0x55667788
- Length Area: 4 bytes, stores allocated length
This way, you can detect if the memory immediately before or after your allocated region has been corrupted.
Custom Malloc #
void *Malloc(size_t __size) {
void *ptr = malloc(4 + 4 + __size + 4); // before + len + data + after
if (!ptr) return NULL;
*((unsigned int*)(ptr)) = 0x11223344; // before red zone
*((unsigned int*)(ptr + 4)) = __size; // length
*((unsigned int*)(ptr + 8 + __size)) = 0x55667788; // after red zone
return ptr + 8; // return data area
}
Memory Check #
void CheckMem(void *data_ptr, size_t __size) {
assert(*((unsigned int*)(data_ptr - 8)) == 0x11223344); // before
assert(*((unsigned int*)(data_ptr - 4)) == __size); // length
assert(*((unsigned int*)(data_ptr + __size)) == 0x55667788); // after
}
If an overwrite occurs, the red zone will be corrupted, and the assertion will immediately trigger.
Conclusion #
Memory overwrite issues in Embedded C are subtle, dangerous, and often difficult to track down.
- Static overwrites often result from array out-of-bounds.
- Dynamic overwrites frequently stem from incorrect allocation sizes or unsafe functions like
strcpy
. - Detection techniques include using tools like Valgrind, or custom guard zones with assertions.
👉 By combining systematic debugging with protective coding strategies, you can detect these issues early—before they turn into elusive and costly bugs.