Facing the Most Challenging ‘Memory Ghost’ in C/C++, Are You Still Using printf?

Hello everyone, I am Xiao Feng Ge.

Memory is a great helper for C/C++ programmers. One of the reasons we often say that C/C++ programs have better performance is that we can manage memory ourselves. However, memory issues also bring endless troubles to C/C++ programmers.

Memory-related bugs are usually very difficult to locate because when the program starts to behave abnormally, it is often far from the actual problem area. Common debugging methods often struggle to identify such issues.

Since these issues are usually caused by memory read and write operations, it would be great if we could observe when a certain memory segment is modified or read. Fortunately, such technology has already been implemented.

An Example

In GDB, you can observe a memory segment by adding a watchpoint. When this memory is modified, the program will stop, allowing us to know exactly which line of code modified that memory. Isn’t this feature powerful?

Next, let’s explain with an example. Here is a piece of code:

#include <iostream>
#include <thread>
using namespace std;

// Thread modifies variable value
void memory_write(int* value) {
  *value = 1;
}

int main()
{
    int a = 10;
    // Get the address of local variable a
    int* c = &a;

    for (int i = 0; i < 100; i++) {
      a += i;
    }

    cout << a << endl;

    // Pass the address of variable a to the thread
    thread t(memory_write, c);
    t.join();

    return 0;
}

This code is very simple. It creates a local variable a, then gets the address of variable a and assigns it to pointer c. After that, it performs an accumulation on variable a and outputs the value of a, which at this point is 4960.

Suppose you later find that the value of variable a has unexpectedly changed to 1. However, due to the complexity of the code, you do not know which part of the code modified variable a. In the above code, we use thread a to simulate this scenario. The thread obtains the address of variable a and modifies it to 1. Next, we use the debugging tool gdb to locate who modified variable a.

Start Capturing the ‘Perpetrator’

Compile the above code, and then use gdb for debugging. Assume the source file is named a.cc, and the compiled executable is named a:

$ gdb a.out
(gdb) b a.cc:20
Breakpoint 1 at 0x400f23: file a.cc, line 20.
(gdb) r
Starting program: /bin/a
Breakpoint 1, main () at a.cc:20
20          cout << a << endl;

The debugging command (b a.cc:20) indicates that we set a breakpoint at line 20 of the code. When the program runs to this point, it will pause. The debugging command r indicates to start running the program. We can see that after running to line 20, it pauses. At this point, let’s check the address of variable a:

(gdb) p &a
$1 = (int *) 0x7fffffffe508

We can see that variable a is located at memory address 0x7fffffffe508. Now, the key point is how to tell gdb to monitor whether the value at memory address 0x7fffffffe508 has been modified? It’s simple:

(gdb) watch *(int*)0x7fffffffe508
Hardware watchpoint 2: *(int*)0x7fffffffe508

We use the watch command to let gdb monitor a memory area starting from 0x7fffffffe508 with a size of 4 bytes (assuming int occupies 4 bytes). This is the meaning of the command watch *(int*)0x7fffffffe508:

In addition, there is something noteworthy in the gdb output:

Hardware watchpoint 2: *(int*)0x7fffffffe508

Pay attention, what is a Hardware watchpoint? Let’s keep that a mystery for now; we will discuss it later. Next, we run the c command in gdb, which means continue, to let the program keep running:

(gdb) c
Continuing.
4960

At this point, line 20 has been executed and the value of variable a, 4960, has been printed. Let’s continue:

[New Thread 0x7ffff6f5c700 (LWP 531823)]
[Switching to Thread 0x7ffff6f5c700 (LWP 531823)]
Hardware watchpoint 2: *(int*)0x7fffffffe508

Old value = 4960
New value = 1
memory_write (value=0x7fffffffe508) at a.cc:8
8       }
(gdb)

Haha, gdb successfully captured which line of code modified the memory at 0x7fffffffe508, and provided us with all the details. We can see that gdb printed that the previous value stored in this memory was 4960, and the modified value is 1, and it was modified at a.cc:8, which is where our thread modified variable a. Gdb successfully captured the ‘perpetrator’; it turns out this thread ‘unintentionally’ modified the value of variable a.

Isn’t it amazing?

I am Xiao Feng Ge, the author of the bestselling book The Secrets of Computer Fundamentals. I would like to recommend my column In-Depth Understanding of Operating Systems, which has been newly upgraded in its second edition, featuring over 600 exquisite hand-drawn illustrations, 87 structured lessons, and a dedicated discussion group (a group of people can go further together). If the operating system has always been a black box for you, then this column is prepared for you:

Leave a Comment