Summary: | [libc] add a freeres function | ||||||
---|---|---|---|---|---|---|---|
Product: | Base System | Reporter: | Paul Floyd <pjfloyd> | ||||
Component: | kern | Assignee: | freebsd-bugs (Nobody) <bugs> | ||||
Status: | New --- | ||||||
Severity: | Affects Some People | CC: | brooks, emaste, lwhsu, markj, obiwac, pjfloyd | ||||
Priority: | --- | ||||||
Version: | Unspecified | ||||||
Hardware: | Any | ||||||
OS: | Any | ||||||
See Also: |
https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=256211 https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=277065 |
||||||
Attachments: |
|
Description
Paul Floyd
2021-10-20 06:10:37 UTC
I'll see if I can work out a patch. In the meantime, here is the canonical C example #include <stdio.h> int main(void) { printf("Hellw, World!\n"); } If I run valgrind --default-suppressions=no --leak-check=full --show-reachable=yes ./hello_world then I get ==1497== 4,096 bytes in 1 blocks are still reachable in loss record 1 of 1 ==1497== at 0x484C8A4: malloc (in /usr/local/libexec/valgrind/vgpreload_memcheck-amd64-freebsd.so) ==1497== by 0x4974AA3: ??? (in /lib/libc.so.7) ==1497== by 0x4987278: ??? (in /lib/libc.so.7) ==1497== by 0x497B012: ??? (in /lib/libc.so.7) ==1497== by 0x497AD89: vfprintf_l (in /lib/libc.so.7) ==1497== by 0x4975AF3: printf (in /lib/libc.so.7) ==1497== by 0x2018A8: main (hello_world.c:5) And for C++ the canonical hello world #include <iostream> int main() { std::cout << "Hello, World!\n"; } valgrind --default-suppressions=no --leak-check=full --show-reachable=yes ./hello_world2 ==1507== 4,096 bytes in 1 blocks are still reachable in loss record 1 of 1 ==1507== at 0x484C8A4: malloc (in /usr/local/libexec/valgrind/vgpreload_memcheck-amd64-freebsd.so) ==1507== by 0x4AB5AA3: ??? (in /lib/libc.so.7) ==1507== by 0x4AC8278: ??? (in /lib/libc.so.7) ==1507== by 0x4AB47D2: ??? (in /lib/libc.so.7) ==1507== by 0x4AB50A3: fwrite (in /lib/libc.so.7) ==1507== by 0x202C08: std::__1::basic_streambuf<char, std::__1::char_traits<char> >::sputn(char const*, long) (include/c++/v1/streambuf:229) ==1507== by 0x202A1F: std::__1::ostreambuf_iterator<char, std::__1::char_traits<char> > std::__1::__pad_and_output<char, std::__1::char_traits<char> >(std::__1::ostreambuf_iterator<char, std::__1::char_traits<char> >, char const*, char const*, char const*, std::__1::ios_base&, char) (include/c++/v1/locale:1411) ==1507== by 0x202725: std::__1::basic_ostream<char, std::__1::char_traits<char> >& std::__1::__put_character_sequence<char, std::__1::char_traits<char> >(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, char const*, unsigned long) (include/c++/v1/ostream:730) ==1507== by 0x20260B: std::__1::basic_ostream<char, std::__1::char_traits<char> >& std::__1::operator<< <std::__1::char_traits<char> >(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, char const*) (include/c++/v1/ostream:869) While looking at this, it strikes me that I'm not reading the libc debuginfo. I need to investigate that. With a debug build of libc, if I run a canonical C 'printf hello world' as follows LD_PRELOAD=/home/paulf/build/src/obj/usr/home/paulf/build/src/amd64.amd64/lib/libc/libc.so.7.full /home/paulf/scratch/valgrind/vg-in-place --default-suppressions=no --leak-check=yes --show-reachable=yes ./hello_world then it tells me that the still reachable memory is ==89763== 4,096 bytes in 1 blocks are still reachable in loss record 1 of 1 ==89763== at 0x484C894: malloc (vg_replace_malloc.c:385) ==89763== by 0x49716D2: __smakebuf (lib/libc/stdio/makebuf.c:73) ==89763== by 0x4984164: __swsetup (lib/libc/stdio/wsetup.c:82) ==89763== by 0x4977D82: __vfprintf (lib/libc/stdio/vfprintf.c:462) ==89763== by 0x4977AF9: vfprintf_l (lib/libc/stdio/vfprintf.c:285) ==89763== by 0x4972703: printf (lib/libc/stdio/printf.c:57) ==89763== by 0x2018A8: main (hello_world.c:5) Looking at the source, calls to printf pass through #define prepwrite(fp) \ ((((fp)->_flags & __SWR) == 0 || \ ((fp)->_bf._base == NULL && ((fp)->_flags & __SSTR) == 0)) && \ __swsetup(fp) This checks that the buffer associated with stdout (also applies to stderr) is allocated and if not allocates it. I'll add a patch in a moment that seems to work, at least for stdout (should work for stderr, not tested) paulf> LD_PRELOAD=/home/paulf/build/src/obj/usr/home/paulf/build/src/amd64.amd64/lib/libc/libc.so.7.full /home/paulf/scratch/valgrind/vg-in-place --default-suppressions=no --leak-check=yes --show-reachable=yes ./hello_world ==69502== Memcheck, a memory error detector ==69502== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. ==69502== Using Valgrind-3.19.0.GIT and LibVEX; rerun with -h for copyright info ==69502== Command: ./hello_world ==69502== Hellw, World! ==69502== ==69502== HEAP SUMMARY: ==69502== in use at exit: 0 bytes in 0 blocks ==69502== total heap usage: 1 allocs, 1 frees, 4,096 bytes allocated ==69502== ==69502== All heap blocks were freed -- no leaks are possible ==69502== ==69502== For lists of detected and suppressed errors, rerun with: -s ==69502== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) Created attachment 229927 [details]
__libc_freeres
Initial prototype. makebuf is where the memory for stdout gets allocated, so I put freeres there. If there are other buffers that I don't know of then maybe a different/new file would be better.
This probably needs protection against being called twice.
No changes needed on the Valgrind side for this!
Presumably there are many other places in libc that need to be hooked. For instance, any use of getgrent(3) will trigger allocation of a global buffer, no? (In reply to Mark Johnston from comment #4) I guess there are probably several other things that would need freeing. GNU libc looks like it calls about 5 functions from __libc_freeres: IO, dl, thread, and a few other more anonymous frees. https://code.woboq.org/userspace/glibc/malloc/set-freeres.c.html In Valgrind I just added suppressions whenever I saw anything that was a one-off allocation in libc. I haven't looked thoroughly at the libc code or written exhaustive Valgrind libc leak tests. Another function to add to the list (not tested, just from looking at the code): setproctitle_internal has 2 static pointers that get malloc'd and never freed. I think this should start with a linker set of pointers to pointers to be freed. That keeps knowledge of things to be freed where they are declared. That's what the last bit of the glibc code does, but spelled somewhat differently than we'd do it. (In reply to Paul Floyd from comment #6) Quick confirmation: =5018== 2,048 bytes in 1 blocks are still reachable in loss record 1 of 2 ==5018== at 0x484CBC4: malloc (vg_replace_malloc.c:397) ==5018== by 0x490B45B: ??? (in /lib/libc.so.7) ==5018== by 0x490B778: setproctitle (in /lib/libc.so.7) ==5018== by 0x2018C3: main (setproctitle.c:6) ==5018== ==5018== 2,048 bytes in 1 blocks are still reachable in loss record 2 of 2 ==5018== at 0x484CBC4: malloc (vg_replace_malloc.c:397) ==5018== by 0x490B4A5: ??? (in /lib/libc.so.7) ==5018== by 0x490B778: setproctitle (in /lib/libc.so.7) ==5018== by 0x2018C3: main (setproctitle.c: |