The following code is an excerpt of my C programm to connect to a server:
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netdb.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdbool.h>
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define PORTNUMBER 443
#define HOSTNAME "www.google.com"
int main() {
struct addrinfo hints, * res, * matchingIP;
char addrstr[99];
char port[20];
snprintf(port, sizeof(port), "%d", PORTNUMBER); // PORTNUMBER as char*
int sock;
memset( & hints, 0, sizeof hints);
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = 0;
hints.ai_flags = AI_CANONNAME;
int status = getaddrinfo(HOSTNAME, port, & hints, & matchingIP); // Memory Leak occurs here?
if (status < 0) {
perror("No IP address found.");
}
for (res = matchingIP; res != NULL; res = res -> ai_next) {
sock = socket(res -> ai_family, res -> ai_socktype, res -> ai_protocol);
if (sock < 0) {
perror("No socket created.");
continue;
}
if (connect(sock, res -> ai_addr, res -> ai_addrlen) < 0) {
perror("Client not connected.");
continue;
}
break;
}
if (res == NULL) {
perror("Client could not connect.");
return EXIT_FAILURE;
}
inet_ntop(res -> ai_family, & ((struct sockaddr_in * ) res -> ai_addr) -> sin_addr, addrstr, sizeof addrstr);
printf("IPv4 address: %s (%s)n", addrstr, res -> ai_canonname);
freeaddrinfo(matchingIP);
}
Now when I run valgrind, it shows there is "still reachable" memory, I’ve narrowed it down to the getaddrinfo
call (the leak does not appear before). Here is the full valgrind log:
==566543== HEAP SUMMARY:
==566543== in use at exit: 3,509 bytes in 8 blocks
==566543== total heap usage: 112 allocs, 104 frees, 112,649 bytes allocated
==566543==
==566543== 38 bytes in 1 blocks are still reachable in loss record 1 of 7
==566543== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==566543== by 0x401F5DE: strdup (strdup.c:42)
==566543== by 0x4019A91: _dl_load_cache_lookup (dl-cache.c:338)
==566543== by 0x400A989: _dl_map_object (dl-load.c:2102)
==566543== by 0x400F514: openaux (dl-deps.c:64)
==566543== by 0x4A17B47: _dl_catch_exception (dl-error-skeleton.c:208)
==566543== by 0x400F962: _dl_map_object_deps (dl-deps.c:248)
==566543== by 0x4015DAF: dl_open_worker (dl-open.c:571)
==566543== by 0x4A17B47: _dl_catch_exception (dl-error-skeleton.c:208)
==566543== by 0x4015609: _dl_open (dl-open.c:837)
==566543== by 0x4A16AE0: do_dlopen (dl-libc.c:96)
==566543== by 0x4A17B47: _dl_catch_exception (dl-error-skeleton.c:208)
==566543==
==566543== 38 bytes in 1 blocks are still reachable in loss record 2 of 7
==566543== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==566543== by 0x400D5B7: _dl_new_object (dl-object.c:196)
==566543== by 0x4006E96: _dl_map_object_from_fd (dl-load.c:997)
==566543== by 0x400A61A: _dl_map_object (dl-load.c:2236)
==566543== by 0x400F514: openaux (dl-deps.c:64)
==566543== by 0x4A17B47: _dl_catch_exception (dl-error-skeleton.c:208)
==566543== by 0x400F962: _dl_map_object_deps (dl-deps.c:248)
==566543== by 0x4015DAF: dl_open_worker (dl-open.c:571)
==566543== by 0x4A17B47: _dl_catch_exception (dl-error-skeleton.c:208)
==566543== by 0x4015609: _dl_open (dl-open.c:837)
==566543== by 0x4A16AE0: do_dlopen (dl-libc.c:96)
==566543== by 0x4A17B47: _dl_catch_exception (dl-error-skeleton.c:208)
==566543==
==566543== 45 bytes in 1 blocks are still reachable in loss record 3 of 7
==566543== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==566543== by 0x401F5DE: strdup (strdup.c:42)
==566543== by 0x4019A91: _dl_load_cache_lookup (dl-cache.c:338)
==566543== by 0x400A989: _dl_map_object (dl-load.c:2102)
==566543== by 0x4015D46: dl_open_worker (dl-open.c:513)
==566543== by 0x4A17B47: _dl_catch_exception (dl-error-skeleton.c:208)
==566543== by 0x4015609: _dl_open (dl-open.c:837)
==566543== by 0x4A16AE0: do_dlopen (dl-libc.c:96)
==566543== by 0x4A17B47: _dl_catch_exception (dl-error-skeleton.c:208)
==566543== by 0x4A17C12: _dl_catch_error (dl-error-skeleton.c:227)
==566543== by 0x4A16C14: dlerror_run (dl-libc.c:46)
==566543== by 0x4A16C14: __libc_dlopen_mode (dl-libc.c:195)
==566543== by 0x49FA8CB: nss_load_library (nsswitch.c:359)
==566543==
==566543== 45 bytes in 1 blocks are still reachable in loss record 4 of 7
==566543== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==566543== by 0x400D5B7: _dl_new_object (dl-object.c:196)
==566543== by 0x4006E96: _dl_map_object_from_fd (dl-load.c:997)
==566543== by 0x400A61A: _dl_map_object (dl-load.c:2236)
==566543== by 0x4015D46: dl_open_worker (dl-open.c:513)
==566543== by 0x4A17B47: _dl_catch_exception (dl-error-skeleton.c:208)
==566543== by 0x4015609: _dl_open (dl-open.c:837)
==566543== by 0x4A16AE0: do_dlopen (dl-libc.c:96)
==566543== by 0x4A17B47: _dl_catch_exception (dl-error-skeleton.c:208)
==566543== by 0x4A17C12: _dl_catch_error (dl-error-skeleton.c:227)
==566543== by 0x4A16C14: dlerror_run (dl-libc.c:46)
==566543== by 0x4A16C14: __libc_dlopen_mode (dl-libc.c:195)
==566543== by 0x49FA8CB: nss_load_library (nsswitch.c:359)
==566543==
==566543== 936 bytes in 2 blocks are still reachable in loss record 5 of 7
==566543== at 0x483DD99: calloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==566543== by 0x401331C: _dl_check_map_versions (dl-version.c:274)
==566543== by 0x40160FC: dl_open_worker (dl-open.c:577)
==566543== by 0x4A17B47: _dl_catch_exception (dl-error-skeleton.c:208)
==566543== by 0x4015609: _dl_open (dl-open.c:837)
==566543== by 0x4A16AE0: do_dlopen (dl-libc.c:96)
==566543== by 0x4A17B47: _dl_catch_exception (dl-error-skeleton.c:208)
==566543== by 0x4A17C12: _dl_catch_error (dl-error-skeleton.c:227)
==566543== by 0x4A16C14: dlerror_run (dl-libc.c:46)
==566543== by 0x4A16C14: __libc_dlopen_mode (dl-libc.c:195)
==566543== by 0x49FA8CB: nss_load_library (nsswitch.c:359)
==566543== by 0x49FB178: __nss_lookup_function (nsswitch.c:467)
==566543== by 0x49BB4BE: gaih_inet.constprop.0 (getaddrinfo.c:800)
==566543==
==566543== 1,200 bytes in 1 blocks are still reachable in loss record 6 of 7
==566543== at 0x483DD99: calloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==566543== by 0x400D283: _dl_new_object (dl-object.c:89)
==566543== by 0x4006E96: _dl_map_object_from_fd (dl-load.c:997)
==566543== by 0x400A61A: _dl_map_object (dl-load.c:2236)
==566543== by 0x400F514: openaux (dl-deps.c:64)
==566543== by 0x4A17B47: _dl_catch_exception (dl-error-skeleton.c:208)
==566543== by 0x400F962: _dl_map_object_deps (dl-deps.c:248)
==566543== by 0x4015DAF: dl_open_worker (dl-open.c:571)
==566543== by 0x4A17B47: _dl_catch_exception (dl-error-skeleton.c:208)
==566543== by 0x4015609: _dl_open (dl-open.c:837)
==566543== by 0x4A16AE0: do_dlopen (dl-libc.c:96)
==566543== by 0x4A17B47: _dl_catch_exception (dl-error-skeleton.c:208)
==566543==
==566543== 1,207 bytes in 1 blocks are still reachable in loss record 7 of 7
==566543== at 0x483DD99: calloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==566543== by 0x400D283: _dl_new_object (dl-object.c:89)
==566543== by 0x4006E96: _dl_map_object_from_fd (dl-load.c:997)
==566543== by 0x400A61A: _dl_map_object (dl-load.c:2236)
==566543== by 0x4015D46: dl_open_worker (dl-open.c:513)
==566543== by 0x4A17B47: _dl_catch_exception (dl-error-skeleton.c:208)
==566543== by 0x4015609: _dl_open (dl-open.c:837)
==566543== by 0x4A16AE0: do_dlopen (dl-libc.c:96)
==566543== by 0x4A17B47: _dl_catch_exception (dl-error-skeleton.c:208)
==566543== by 0x4A17C12: _dl_catch_error (dl-error-skeleton.c:227)
==566543== by 0x4A16C14: dlerror_run (dl-libc.c:46)
==566543== by 0x4A16C14: __libc_dlopen_mode (dl-libc.c:195)
==566543== by 0x49FA8CB: nss_load_library (nsswitch.c:359)
==566543==
==566543== LEAK SUMMARY:
==566543== definitely lost: 0 bytes in 0 blocks
==566543== indirectly lost: 0 bytes in 0 blocks
==566543== possibly lost: 0 bytes in 0 blocks
==566543== still reachable: 3,509 bytes in 8 blocks
==566543== suppressed: 0 bytes in 0 blocks
==566543==
==566543== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
I’ve read online several times that while "still reachable" memory leaks are memory leaks, they are not ones I should worry about. Sadly for me, it is a formal requirement that valgrind reports absolutely no memory leaks, including still reachable. Is there any chance I am able to fix it or is it the fault of the library? If the latter – what are suitable alternatives?
As far as I am are the host runs using IPv6 – perhaps that’s relevant.
Edit: The project is compiled with the -std=c99
flag and defines the posix C source as #define _POSIX_C_SOURCE 200809L
. The valgrind flags are valgrind --leak-check=full --show-leak-kinds=all --trace-children=yes --log-file=log.txt --track-origins=yes -s
and I’m developing on Ubuntu 20.04.6 LTS
Another edit: Minimal reproducible example
3
Answers
It’s not really a fault, but as you noted (and was also mentioned in the comments)
libc
decided it needs to bind tolibnss
. There are at least a couple of ways to confirm that:LD_DEBUG=bindings
(e.g.,LD_DEBUG=bindings ./program
)getchar()
to pause your program before exiting and examine/proc/<pid>/maps
.Unfortunatelly, there’s no reliable or portable way to
dl_close()
that I’m aware of.The best alternative is to provide
valgrind
with a suppressions file which will make it ignore specific allocations/leaks/reachable blocks.You can generate one specifically for this case by running
valgrind
as you did but adding the option--gen-suppressions=yes
.My opinion is that this is most likely a GNU libc bug.
Oh look.
Valgrind saying it’s a GNU libc bug
GNU libc bug
GNU libc includes a cleanup function,
__libc_freeres()
that gets called by Valgrind upon a clean exit. It sounds like someone added some dynamic loading and there’s missing cleanup of the dynamic loading name cache from __libc_freeres.I could add that to the Valgrind default supressions. That would only be the last resort if the GNU libc devs refuse to fix the issue. The above bug has been open for getting on for 2 years so it’s not looking very optimistic.
If the goal there is to improve your software quality that’s a good thing. But if the goal is only to give the appearance of quality and to enable the suits to make powerpoint presentatons with a page showing zero leaks then that’s a bad thing. That’s a slippery slope that leads to huge suppression files that risk hiding genuine issues.
Having said that, personally I would add a suppression for this.
And going through your options
Does your executable spawn child processes? If not you don’t need
--trace-children=yes
. On the other hand if you do spawn and need--trace-children=yes
then you should by using the pid in the log file name, such as--log-file=log.%p,txt
where %p will be replaced by the PID. If you don’t do that then the child log files will overwrite parent ones.I don’t recommend using
--track-origins=yes
until after you have found errors. It causes a fairly significant slowdown. If you’re OK with that overhead then the advantage is that it will have more info to start with.-s
if you are really serious then you will check that any suppressions that you have added are still in use and remove them when they are no longer used.