Title: Memory leak detection wizard Valgrind

Categories :[Memory detection]

tags:[valgrind]

date: 2022/01/08

Author: hackett

Wechat official account: Overtime Ape


1. An overview of the

Valgrind is a software development tool for memory debugging, memory leak detection, and performance analysis.

The Valgrind release currently includes seven production-quality tools: a memory error detector, two thread error detectors, a cache and branch prediction analyzer, a call graph generation cache and branch prediction analyzer, and two different heap analyzers. It also includes an experimental SimPoint basic block vector generator. It runs on the following platforms: X86/Linux, AMD64/Linux, ARM/Linux, ARM64/Linux, PPC32/Linux, PPC64/Linux, PPC64LE/Linux, S390X/Linux, MIPS32/Linux, MIPS64/Linux, X86/Solaris, AMD64/Solaris, ARM/Android (2.3.x and later), ARM64/Android, X86/Android (4.0 and later), MIPS32/Android, X86/FreeBSD, AMD64/FreeBSD, X86/Darwin and AMD64/Darwin (Mac OS X 10.12)

1.1 tools

It generally contains the following tools:

1.Memcheck

The most common tool used to detect memory problems in programs. All reads and writes to memory are detected, and all calls to malloc()/free()/new/delete are captured. Therefore, it can detect the following problems:

  • Use of uninitialized memory;
  • Read/write freed memory block;
  • Read/write more than malloc allocated memory block
  • Read/write improper stack blocks
  • Memory leaks, in which Pointers to a block of memory are lost forever;
  • Incorrect malloc/free or new/delete match;
  • The DST and SRC Pointers in the memcpy() correlation function overlap.

2.Callgrind

An analysis tool similar to Gprof, but with a much more nuanced view of how programs work, it can give us much more information. Unlike Gprof, it does not require special options to compile source code, but debugging options are recommended. Callgrind collects runtime data, builds function call diagrams, and optionally performs cache emulation. At the end of the run, it writes the analysis data to a file. Callgrind_annotate converts the contents of this file into a readable form.

3.Cachegrind

The Cache analyzer, which simulates level 1, Dl, and level 2 caches in the CPU, can pinpoint Cache misses and hits in programs. It also provides the number of cache misses, the number of memory references, and the number of instructions per line of code, per function, per module, and the entire program, if needed. This is a great help in optimizing your program.

4.Helgrind

It is mainly used to check race problems in multithreaded programs. Helgrind looks for areas of memory that are accessed by multiple threads and are not consistently locked. These areas tend to get out of sync between threads and can lead to hard-to-discover errors. Helgrind implements a competition detection algorithm called “Eraser” and makes further improvements to reduce the number of reported errors. Helgrind is still experimental, though.

5.Massif

Stack analyser, which measures how much memory a program uses in the stack, tells us the size of the heap block, heap management block and stack. Massif helps us reduce memory usage and, on modern systems with virtual memory, speeds up the execution of our programs, reducing the chance that they will get stuck in swap. Lackey and NulGrind will also be available. Lackey is a small tool that is rarely used; Nulgrind just shows developers how to build a tool.

1.2 the principle

Memcheck can detect memory problems because it creates two global tables.

Valid – the Value table

For every byte in the entire address space of a process, there are eight bits. There is also a bit vector for each CPU register. These bits record whether the byte or register value has a valid, initialized value.

Valid Address table

For each byte in the process’s entire address space, there is a corresponding bit that records whether the address can be read or written.

Detection principle:

When reading or writing A byte in memory, first check the A bit corresponding to the byte. If the A bit shows that the location is invalid, memCheck reports A read/write error. The core is similar to a virtual CPU environment, so that when a byte of memory is loaded into the real CPU, the corresponding V bit of that byte is also loaded into the virtual CPU environment. If a value in a register is used to generate a memory address, or if the value can affect program output, memCheck checks the corresponding V bits and reports an error using uninitialized memory if the value has not been initialized.

2. Installation and use

2.1 download

Go to valgrind.org/ to download the latest version

Centos download:

yum install valgrind
Copy the code

Ubuntu download

sudo apt-get install valgrind
Copy the code

2.2 the use of

Valgrind [options] prog-and-args [options]:

Common option for all Valgrind tools

-tool= Indicates the most common option. Run the tool called ToolName in ValGrind. The default memcheck. H -help Displays the help information.

-version Displays the version of the ValGrind kernel. Each tool has its own version.

Q-quiet Runs quietly, printing only error messages.

V – verbose For more detailed information, increase the number of errors.

– the trace – children = no | yes tracking the child thread? [no]

– track – FDS = no | yes tracking open file description? [no]

– time – stamp = no | yes add a timestamp to the LOG information? [no]

-log-fd= output log to descriptor file [2=stderr]

-log-file= Writes the output information to a file named filename.PID.PID is the running ID of the program

-log-file-exactly= Outputs log information to file

-log-file-qualifier= Obtains the value of the environment variable as the file name of the output information. [none]

-log-socket=ipaddr:port Outputs logs to the socket. Ipaddr :port

LOG information output:

-xml=yes Outputs the information in XML format. Only memcheck is available

-num-callers= show callers in stack traces [12]

– error – limit = no | yes if too many mistakes, displayed a new stop error? [yes]

-error-exitCode = Return error code if an error is found [0=disable]

– db – attach = no | yes when there is an error, valgrind will automatically start the debugger GDB. [no]

-db-command= start debugger command line option [gdb-nw %f %p]

Related options for the Memcheck tool:

– leak – check = no | summary | full requirements of leak give detailed information? [summary]

-leak-resolution=low|med|high how much bt merging in leak check [low]

-show-reachable=no|yes show reachable blocks in leak check? [no]

3. Application examples

3.1 Array Out of bounds

malloc1.c

#include <stdio.h> int main(int argc, char **argv) { int *x = malloc(8*sizeof(int)); x[9] = 0; // Array subscript out of bounds free(x); return 0; }Copy the code

Compile:

gcc -Wall malloc1.c -g -o malloc1
Copy the code

Use Valgrind to check program bugs:

Valgrind --tool=memcheck --leak-check=full./malloc1Copy the code

Running results:

[root@hackett valgrind]# valgrind --tool=memcheck --leak-check=full ./malloc1
==550168== Memcheck, a memory error detector
==550168== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==550168== Using Valgrind-3.17.0 and LibVEX; rerun with -h for copyright info
==550168== Command: ./malloc1
==550168== 
==550168== Invalid write of size 4
==550168==    at 0x4005FB: main (malloc1.c:7)
==550168==  Address 0x520b064 is 4 bytes after a block of size 32 alloc'd
==550168==    at 0x4C360A5: malloc (vg_replace_malloc.c:380)
==550168==    by 0x4005EE: main (malloc1.c:5)
==550168== 
==550168== 
==550168== HEAP SUMMARY:
==550168==     in use at exit: 0 bytes in 0 blocks
==550168==   total heap usage: 1 allocs, 1 frees, 32 bytes allocated
==550168== 
==550168== All heap blocks were freed -- no leaks are possible
==550168== 
==550168== For lists of detected and suppressed errors, rerun with: -s
==550168== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
Copy the code

Analysis:

1. The number at the beginning of each line indicates the process ID, which is 550168

2, ==550168== Invalid write of size 4 ==550168== at 0x4005BB: main (malloc1.c:7)

The two lines show where the error occurred. X allocated 10 bytes of space but wrote data to the 11th byte, so the error “Invalid Write” is displayed. The code is on line 7 of malloc1.c

3.2 Read and Write Data after memory release

malloc2.c

#include <stdio.h> int main(int argc, char **argv) { char *p = malloc(1); *p = 'a'; char c = *p; printf("[%c]\n",c); free(p); // release c = *p; // Read return 0; }Copy the code

Compile:

gcc -Wall malloc2.c -g -o malloc2
Copy the code

Use Valgrind to check program bugs:

Valgrind --tool=memcheck --leak-check=full./malloc2Copy the code

Running results:

[root@hackett valgrind]# valgrind --tool=memcheck --leak-check=full ./malloc2 ==550063== Memcheck, a memory error detector ==550063== Copyright (C) 2002-2017, and GNU GPL'd, By Julian Seward et al. ==550063== Using Valgrind-3.17.0 and LibVEX; rerun with -h for copyright info ==550063== Command: ./malloc2 ==550063== [a] ==550063== Invalid read of size 1 ==550063== at 0x400679: main (malloc2.c:15) ==550063== Address 0x520b040 is 0 bytes inside a block of size 1 free'd ==550063== at 0x4C38A03: free (vg_replace_malloc.c:755) ==550063== by 0x400674: main (malloc2.c:13) ==550063== Block was alloc'd at ==550063== at 0x4C360A5: malloc (vg_replace_malloc.c:380) ==550063== by 0x40063E: main (malloc2.c:5) ==550063== ==550063== ==550063== HEAP SUMMARY: ==550063== in use at exit: 0 bytes in 0 blocks ==550063== total heap usage: 2 allocs, 2 frees, Allocated ==550063== ==550063== All heap blocks were freed -- no leaks are possible ==550063== ==550063== For lists of detected and suppressed errors, rerun with: -s ==550063== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)Copy the code

Analysis:

1. The number at the beginning of each line represents the process ID. The process ID in this case is 550063

2, ==550063== Invalid read of size 1 ==550063== at 0x400679: main (malloc2.c:15) ==550063== Address 0x520b040 is 0 bytes inside a block of size 1 free’d ==550063== at 0x4C38A03: free (vg_replace_malloc.c:755) ==550063== by 0x400674: main (malloc2.c:13)

The five lines show where the error occurred, i.e. the memory pointed to by P has been freed and is still being read, so an “Invalid read” error is displayed at line 15 of malloc2.c

3.3 Invalid Read/Write

malloc3.c

#include <stdio.h> int main(int argc, char **argv) { char *p = malloc(1); *p = 'a'; char c = *(p+1); Printf ("[%c]\n",c); free(p); // Release return 0; }Copy the code

Compile:

gcc -Wall malloc3.c -g -o malloc3
Copy the code

Use Valgrind to check program bugs:

Valgrind --tool=memcheck --leak-check=full./malloc3Copy the code

Running results:

[root@iZwz97bu0gr8vx0j8l6kkzZ valgrind]# valgrind --tool=memcheck --leak-check=full ./malloc3 ==550135== Memcheck, a memory error detector ==550135== Copyright (C) 2002-2017, and GNU GPL'd, By Julian Seward et al. ==550135== Using Valgrind-3.17.0 and LibVEX; rerun with -h for copyright info ==550135== Command: ./malloc3 ==550135== ==550135== Invalid read of size 1 ==550135== at 0x40064E: main (malloc3.c:9) ==550135== Address 0x520b041 is 0 bytes after a block of size 1 alloc'd ==550135== at 0x4C360A5: malloc (vg_replace_malloc.c:380) ==550135== by 0x40063E: main (malloc3.c:5) ==550135== [] ==550135== ==550135== HEAP SUMMARY: ==550135== in use at exit: 0 bytes in 0 blocks ==550135== total heap usage: 2 allocs, 2 frees, Allocated ==550135== ==550135== All heap blocks were freed -- no leaks are possible ==550135== ==550135== For lists of detected and suppressed errors, rerun with: -s ==550135== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)Copy the code

Analysis:

1. The number at the beginning of each line represents the process ID. The process ID in this case is 550135

2, ==550135== Invalid read of size 1 ==550135== at 0x40064E: main (malloc3.c:9)

The five lines show where the error occurred, i.e. reading from unallocated memory, so the error “Invalid read” is displayed on line 9 of malloc3.c

3.4 Memory Leakage

malloc4.c

#include <stdio.h> int main(int argc, char **argv) { char *p = malloc(1); *p = 'a'; char c = *p; printf("[%c]\n",c); Return 0; }Copy the code

Compile:

gcc -Wall malloc4.c -g -o malloc4
Copy the code

Use Valgrind to check program bugs:

Valgrind --tool=memcheck --leak-check=full./malloc4Copy the code

Running results:

[root@hackett valgrind]# valgrind --tool=memcheck --leak-check=full ./malloc4 ==550195== Memcheck, a memory error detector ==550195== Copyright (C) 2002-2017, and GNU GPL'd, By Julian Seward et al. ==550195== Using Valgrind-3.17.0 and LibVEX; rerun with -h for copyright info ==550195== Command: ./malloc4 ==550195== [a] ==550195== ==550195== HEAP SUMMARY: ==550195== in use at exit: 1 bytes in 1 blocks ==550195== total heap usage: 2 allocs, 1 frees, Allocated ==550195== ==550195== 1 bytes in 1 blocks are definitely lost in loss record 1 of 1 ==550195== at 0x4C360A5: malloc (vg_replace_malloc.c:380) ==550195== by 0x4005EE: main (malloc4.c:5) ==550195== ==550195== LEAK SUMMARY: ==550195== definitely lost: 1 bytes in 1 blocks ==550195== indirectly lost: 0 bytes in 0 blocks ==550195== possibly lost: 0 bytes in 0 blocks ==550195== still reachable: 0 bytes in 0 blocks ==550195== suppressed: 0 bytes in 0 blocks ==550195== ==550195== For lists of detected and suppressed errors, rerun with: -s ==550195== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)Copy the code

Analysis:

1. The number at the beginning of each line represents the process ID. The process ID in this case is 550195

2, ==550195== 1 bytes in 1 blocks are definitely lost in loss record 1 of 1 ==550195== at 0x4C360A5: malloc (vg_replace_malloc.c:380) ==550195== by 0x4005EE: main (malloc4.c:5)

These three lines show the location of the error, which is the memory leak of stack trace. The code in line 5 of malloc1.c has applied for memory without corresponding free

3.5 Memory Release Multiple times

malloc5.c

#include <stdio.h> int main(int argc, char **argv) { char *p = malloc(1); *p = 'a'; char c = *p; // Add 1 printf("[%c]\n",c); free(p); free(p); // Free (p); Return 0; }Copy the code

Compile:

gcc -Wall malloc5.c -g -o malloc5
Copy the code

Use Valgrind to check program bugs:

Valgrind --tool=memcheck --leak-check=full./malloc5Copy the code

Running results:

[root@hackett valgrind]# valgrind --tool=memcheck --leak-check=full ./malloc5 ==550227== Memcheck, a memory error detector ==550227== Copyright (C) 2002-2017, and GNU GPL'd, By Julian Seward et al. ==550227== Using Valgrind-3.17.0 and LibVEX; rerun with -h for copyright info ==550227== Command: ./malloc5 ==550227== [a] ==550227== Invalid free() / delete / delete[] / realloc() ==550227== at 0x4C38A03: free (vg_replace_malloc.c:755) ==550227== by 0x400680: main (malloc5.c:14) ==550227== Address 0x520b040 is 0 bytes inside a block of size 1 free'd ==550227== at 0x4C38A03: free (vg_replace_malloc.c:755) ==550227== by 0x400674: main (malloc5.c:13) ==550227== Block was alloc'd at ==550227== at 0x4C360A5: malloc (vg_replace_malloc.c:380) ==550227== by 0x40063E: main (malloc5.c:5) ==550227== ==550227== Invalid free() / delete / delete[] / realloc() ==550227== at 0x4C38A03: free (vg_replace_malloc.c:755) ==550227== by 0x40068C: main (malloc5.c:15) ==550227== Address 0x520b040 is 0 bytes inside a block of size 1 free'd ==550227== at 0x4C38A03: free (vg_replace_malloc.c:755) ==550227== by 0x400674: main (malloc5.c:13) ==550227== Block was alloc'd at ==550227== at 0x4C360A5: malloc (vg_replace_malloc.c:380) ==550227== by 0x40063E: main (malloc5.c:5) ==550227== ==550227== ==550227== HEAP SUMMARY: ==550227== in use at exit: 0 bytes in 0 blocks ==550227== total heap usage: 2 allocs, 4 frees, Allocated ==550227== ==550227== All heap blocks were freed -- no leaks are possible ==550227== ==550227== For lists of detected and suppressed errors, rerun with: -s ==550227== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)Copy the code

Analysis:

1. The number at the beginning of each line represents the process ID. The process ID in this case is 550227

2, ==550227== Invalid Free ()/delete/delete[] / realloc() ==550227== at 0x4C38A03: free (vg_replace_malloc.c:755) ==550227== by 0x400680: main (malloc5.c:14)

The three lines show the location of the first error, which is the multiple free memory problem, so the error “Invalid free()/delete/delete[] / realloc()” is displayed on line 14 of malloc5.c

3, ==550227== Invalid Free ()/delete/delete[] / realloc() ==550227== at 0x4C38A03: free (vg_replace_malloc.c:755) ==550227== by 0x40068C: main (malloc5.c:15)

These three lines show the location of the second error, which is the multiple free memory problem, so the error “Invalid free()/delete/delete[] / realloc()” is displayed on line 15 of malloc5.c

3.6 Dynamic Memory Management

There are three common memory allocation methods: static storage, stack allocation, and heap allocation. Global variables are stored statically and are allocated at compile time. Local variables in functions are allocated on the stack. The most flexible use of memory is heap allocation, also known as dynamic allocation. Commonly used dynamic memory allocation functions include: malloc, alloc, realloc, new, etc. Dynamic release functions include free, delete.

Once we have successfully requested dynamic memory, we need to manage it ourselves, which is the most error-prone.

malloc6.c

#include <stdio.h> int main(int argc, char **argv) {int I; char *p = (char *)malloc(10); char *pt = p; for(i = 0; i < 10; i++) { p[i] = 'A'+i; } free(p); pt[1] = 'x'; free(pt); return 0; }Copy the code

Compile:

gcc -Wall malloc6.c -g -o malloc6
Copy the code

Use Valgrind to check program bugs:

Valgrind --tool=memcheck --leak-check=full./malloc6Copy the code

Running results:

[root@hackett valgrind]# valgrind --tool=memcheck --leak-check=full ./malloc6
==550239== Memcheck, a memory error detector
==550239== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==550239== Using Valgrind-3.17.0 and LibVEX; rerun with -h for copyright info
==550239== Command: ./malloc6
==550239== 
==550239== Invalid write of size 1
==550239==    at 0x400639: main (malloc6.c:16)
==550239==  Address 0x520b041 is 1 bytes inside a block of size 10 free'd
==550239==    at 0x4C38A03: free (vg_replace_malloc.c:755)
==550239==    by 0x400630: main (malloc6.c:14)
==550239==  Block was alloc'd at
==550239==    at 0x4C360A5: malloc (vg_replace_malloc.c:380)
==550239==    by 0x4005EE: main (malloc6.c:7)
==550239== 
==550239== Invalid free() / delete / delete[] / realloc()
==550239==    at 0x4C38A03: free (vg_replace_malloc.c:755)
==550239==    by 0x400647: main (malloc6.c:18)
==550239==  Address 0x520b040 is 0 bytes inside a block of size 10 free'd
==550239==    at 0x4C38A03: free (vg_replace_malloc.c:755)
==550239==    by 0x400630: main (malloc6.c:14)
==550239==  Block was alloc'd at
==550239==    at 0x4C360A5: malloc (vg_replace_malloc.c:380)
==550239==    by 0x4005EE: main (malloc6.c:7)
==550239== 
==550239== 
==550239== HEAP SUMMARY:
==550239==     in use at exit: 0 bytes in 0 blocks
==550239==   total heap usage: 1 allocs, 2 frees, 10 bytes allocated
==550239== 
==550239== All heap blocks were freed -- no leaks are possible
==550239== 
==550239== For lists of detected and suppressed errors, rerun with: -s
==550239== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
Copy the code

Analysis:

1. The number at the beginning of each line represents the process ID. The process ID in this case is 550239

2, ==550239== Invalid write of size 1 ==550239== at 0x400639: main (malloc6.c:16) 2, ==550239== Invalid write of size 1 ==550239== at 0x400639: main (malloc6.c:16)

This line shows that there is an error in the form of an Invalid write operation, so an Invalid write error is displayed on line 16 of malloc6.c

5, == Invalid free()/delete/delete[] / realloc() ==550239== at 0x4C38A03: free (vg_replace_malloc.c:755) ==550239== by 0x400647: main (malloc6.c:18) ==550239== Address 0x520b040 is 0 bytes inside a block of size 10 free’d ==550239== at 0x4C38A03: free (vg_replace_malloc.c:755) ==550239== by 0x400630: main (malloc6.c:14)

The five lines show where the second error occurred. Pointers P and pt refer to the same block of memory, but were freed twice. The system maintains a linked list of dynamic memory on the heap. If freed, this means that the block of memory can continue to be allocated to other parts of the heap. If the memory is freed and then accessed, it may overwrite other parts of the heap.

Conclusion:

1. The application will be released after completion of use. If it does not, or if it does not, it is a memory leak; Overrelease can also cause problems.

2. Pay attention to the size of the array and do not read or write illegal memory

3. Malloc and new correspond to free and delete.

If you think the article is good, you can give it a “triple link”, and the article will be synchronized to your personal wechat official account [Overtime Ape].

I’m Hackett. I’ll see you next time