Debug C/C++ Programs in Docker Container

Introduction

Compile and debug C/C++ programs sometimes take a lot of time. So it is a good idea to make the full use of the powerful CPU on the server and move all the compile, debug, and test processes there. To do jobs on the server nowadays, usually Docker is a requirement.

In this blog post, I am going to show how to debug C/C++ programs for logic errors, segmentation faults, and memory leaks, using CMake, GDB and Valgrind in Docker containers.

GitHub

The C++ examples and Dockerfile could be found in C++ Debug Docker on GitHub.

Docker

Create Docker Image

The basic Docker image for C++ debugging has CMake, GDB, and Valgrind installed.

1
$ docker build -f cpp.Dockerfile -t cpp-debug:0.0.1 .

Start Docker Container

1
$ docker run -it --rm --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -v $(pwd):/mnt cpp-debug:0.0.1

The arguments --cap-add=SYS_PTRACE and --security-opt seccomp=unconfined are required for C++ memory profiling and debugging in Docker.

Examples

Build Examples

CMake was used to build the C++ programs. Please run the following command in the terminal to build.

1
2
3
4
$ mkdir build
$ cd build
$ cmake ..
$ make

To make the C++ program GDB compatible, we set CMAKE_BUILD_TYPE to be Debug by adding the following line of code to the CMakeLists.txt file.

1
set(CMAKE_BUILD_TYPE Debug)

Setting CMAKE_BUILD_TYPE to be RelWithDebInfo also allows GDB to debug, and the performance of the program compiled should not be affected if it is running without GDB. However, the debugging process might be different from the program compiled with Debug since the code had been optimized by the compiler.

Logical Error

The logical error example was taken from “How to Debug Using GDB” with slight modification.

When we ran the program, we would find that the program did not return values as expected.

1
2
3
4
5
6
7
8
$ ./logicalError 
This program is used to compute the value of the following series :
(x^0)/0! + (x^1)/1! + (x^2)/2! + (x^3)/3! + (x^4)/4! + ........ + (x^n)/n!
Please enter the value of x : 2

Please enter an integer value for n : 3

The value of the series for the values entered is inf

We started to use GDB to debug this program.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ gdb ./logicalError 
GNU gdb (Ubuntu 8.1-0ubuntu3.2) 8.1.0.20180409-git
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./logicalError...done.
(gdb)

We created a breaking point to the ComputeSeriesValue function.

1
2
(gdb) break ComputeSeriesValue(double, int) 
Breakpoint 1 at 0xc40: file /mnt/header.cpp, line 18.

It is equivalent to use b for break.

We started to run the program and stopped at the breaking point we just created.

1
2
3
4
5
6
7
8
9
10
11
(gdb) run
Starting program: /mnt/build/logicalError
This program is used to compute the value of the following series :
(x^0)/0! + (x^1)/1! + (x^2)/2! + (x^3)/3! + (x^4)/4! + ........ + (x^n)/n!
Please enter the value of x : 2

Please enter an integer value for n : 3


Breakpoint 1, ComputeSeriesValue (x=2, n=3) at /mnt/header.cpp:18
18 double seriesValue = 0.0;

We suspected that the ComputeFactorial is problematic. We ran the program step by step using next or n several times.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(gdb) next
19 double xpow = 1;
(gdb) n
21 for (int k = 0; k <= n; k ++) {
(gdb) n
22 seriesValue += xpow / ComputeFactorial(k);
(gdb) n
23 xpow = xpow * x;
(gdb) n
21 for (int k = 0; k <= n; k ++) {
(gdb) n
22 seriesValue += xpow / ComputeFactorial(k);
(gdb) n
23 xpow = xpow * x;
(gdb) n
21 for (int k = 0; k <= n; k ++) {
(gdb) n
22 seriesValue += xpow / ComputeFactorial(k);

When we reach a line of code containing ComputeFactorial, we step into the function ComputeFactorial using step or s.

1
2
3
(gdb) step
ComputeFactorial (number=2) at /mnt/header.cpp:6
6 int fact = 0;

We could also print the value of fact using print.

1
2
(gdb) print fact
$1 = 0

We executed ComputeFactorial further but found the value of fact remains to be 0 before it is returned.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(gdb) print fact
$1 = 0
(gdb) n
10 for (int j = 1; j <= number; j ++) {
(gdb) n
11 fact = fact * j;
(gdb) n
10 for (int j = 1; j <= number; j ++) {
(gdb) n
11 fact = fact * j;
(gdb) n
10 for (int j = 1; j <= number; j ++) {
(gdb) n
14 return fact;
(gdb) print fact
$2 = 0

So we realized that there is something wrong with fact.

We could also know where the execution is by viewing the stack using backtrace or bt.

1
2
3
4
(gdb) backtrace
#0 ComputeFactorial (number=2) at /mnt/header.cpp:14
#1 0x0000555555554c6f in ComputeSeriesValue (x=2, n=3) at /mnt/header.cpp:22
#2 0x0000555555554b3c in main () at /mnt/logicalError.cpp:25

Core Dumped

The core dumped example was also taken from “How to Debug Using GDB” with slight modification.

1
2
$ ./coreDumped 
Segmentation fault (core dumped)

Sometimes it is easier to find where to problem is for Segmentation fault (core dumped) using GDB. A simple bt would work.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ gdb ./coreDumped 
GNU gdb (Ubuntu 8.1-0ubuntu3.2) 8.1.0.20180409-git
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./coreDumped...done.
(gdb) run
Starting program: /mnt/build/coreDumped

Program received signal SIGSEGV, Segmentation fault.
0x000055555555466c in main () at /mnt/coreDumped.cpp:14
14 temp[3] = 'F';
(gdb) bt
#0 0x000055555555466c in main () at /mnt/coreDumped.cpp:14

It told us the problem is in the line 14 of coreDumped.cpp.

Memory Leak

GDB is not able to find memory leak. Valgrind comes into play. We executed the program using Valgrind and an argument --leak-check=yes to see whether there are potential risks of memory leak.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ valgrind --leak-check=yes ./memoryLeak 
==471== Memcheck, a memory error detector
==471== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==471== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==471== Command: ./memoryLeak
==471==
==471==
==471== HEAP SUMMARY:
==471== in use at exit: 12 bytes in 1 blocks
==471== total heap usage: 2 allocs, 1 frees, 72,716 bytes allocated
==471==
==471== 12 bytes in 1 blocks are definitely lost in loss record 1 of 1
==471== at 0x4C3089F: operator new[](unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==471== by 0x10868B: main (memoryLeak.cpp:3)
==471==
==471== LEAK SUMMARY:
==471== definitely lost: 12 bytes in 1 blocks
==471== indirectly lost: 0 bytes in 0 blocks
==471== possibly lost: 0 bytes in 0 blocks
==471== still reachable: 0 bytes in 0 blocks
==471== suppressed: 0 bytes in 0 blocks
==471==
==471== For counts of detected and suppressed errors, rerun with: -v
==471== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

It told us the problem is in the line 3 of memoryLeak.cpp.

Final Remarks

The C++ bugs we would have for real applications are usually much harder to debug. But the basic debugging skills mentioned above are requirements.

References

Debug C/C++ Programs in Docker Container

https://leimao.github.io/blog/Debug-CPP-In-Docker-Container/

Author

Lei Mao

Posted on

01-11-2020

Updated on

01-11-2020

Licensed under


Comments