|
From: Chris T. <chr...@gm...> - 2018-12-13 00:07:42
|
I am getting confusing output from valgrind when running the memory checker
on my pthread code. Specifically, it seems to not like the use of
the pthread_attr_setstack() function. I have reduced my problem to a small
example, pasted at the end of this message. What I'm trying to do is:
1. Allocate a 64KB buffer for use as the thread stack
2. Call pthread_attr_setstack() to set the stack attributes
3. Call pthread_create(), let the thread run to completion, and then
pthread_join()
4. Clear out the threads stack via memset() to ensure I don't leave any
interesting data in the heap.
5. free() the thread stack
This all seems to work fine, but valgrind complains that the memset() in
step 4 is an invalid write. An example of this error is here:
==26698== Invalid write of size 8
==26698== at 0x4C3665A: memset (in
/usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==26698== by 0x108A4E: main (in /home/cteague/develop/hello/pthread_test)
==26698== Address 0x545ae88 is 59,912 bytes inside a block of size 65,536
alloc'd
==26698== at 0x4C2FB0F: malloc (in
/usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==26698== by 0x1089B6: main (in /home/cteague/develop/hello/pthread_test)
I am very confused. How can it be an invalid write inside my own block of
allocated memory? It looks to me like a problem with valgrind - but my
experience has been that valgrind is incorrect far less frequently than I
am! My command line is this:
valgrind --leak-check=full ./pthread_test
And my test code is here below. I would really appreciate any insight into
this problem. A few notes:
- I thought the problem was the "stack guard" in pthread, but
using pthread_attr_setguardsize() to set it to 0 did not help.
- Yes, I do need to use pthread_attr_setstack(). I am on an embedded
platform where I need to specify from which memory the stack will live in.
- I have reproduced this on valgrind 3.10 and 3.13
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
static void* thread_start(void *arg)
{
printf("thread running\n");
return NULL;
}
int main(int argc, char **argv)
{
pthread_t thd;
pthread_attr_t thd_attr;
size_t stack_size = 64*1024;
void* p_stack;
printf("start\n");
p_stack = malloc(stack_size);
if (NULL != p_stack) {
if (0 == pthread_attr_init(&thd_attr)) {
if (0 == pthread_attr_setstack(&thd_attr, p_stack, stack_size)) {
if (0 == pthread_create(&thd, &thd_attr, thread_start, NULL)) {
printf("thread created\n");
pthread_join(thd, NULL);
}
}
pthread_attr_destroy(&thd_attr);
}
memset(p_stack, 'A', stack_size);
free(p_stack);
}
printf("done\n");
return 0;
}
|
|
From: John R. <jr...@bi...> - 2018-12-13 05:34:55
|
> - I have reproduced this on valgrind 3.10 and 3.13 It works correctly (no complaints) on x86_64 under valgrind 3.14 as distributed by Fedora 28 in valgrind-3.14.0-1.fc28.x86_64.rpm . $ rpm -q valgrind valgrind-3.14.0-1.fc28.x86_64 $ type valgrind valgrind is hashed (/usr/bin/valgrind) $ valgrind --version valgrind-3.14.0 $ gcc -g -O pthread_test.c -lpthread $ valgrind ./a.out ==18824== Memcheck, a memory error detector ==18824== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. ==18824== Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright info ==18824== Command: ./a.out ==18824== start thread created thread running done ==18824== ==18824== HEAP SUMMARY: ==18824== in use at exit: 0 bytes in 0 blocks ==18824== total heap usage: 3 allocs, 3 frees, 66,832 bytes allocated ==18824== ==18824== All heap blocks were freed -- no leaks are possible ==18824== ==18824== For counts of detected and suppressed errors, rerun with: -v ==18824== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) $ |
|
From: Chris T. <chr...@gm...> - 2018-12-13 06:39:40
|
Strange. I only have an Ubuntu 18.04 machine and the latest valgrind is 3.13. I downloaded the Fedora 28 docker image and built in there, and it definitely complains. Output below: [root@4fe07db648b4 home]# gcc p.c -lpthread -o p [root@4fe07db648b4 home]# ./p start thread created thread running done [root@4fe07db648b4 home]# valgrind ./p ==137== Memcheck, a memory error detector ==137== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. ==137== Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright info ==137== Command: ./p ==137== start thread created thread running ==137== Invalid write of size 8 ==137== at 0x4C35E5F: memset (vg_replace_strmem.c:1251) ==137== by 0x400873: main (in /home/p) ==137== Address 0x54281d8 is 60,760 bytes inside a block of size 65,536 alloc'd ==137== at 0x4C2EE0B: malloc (vg_replace_malloc.c:299) ==137== by 0x4007DF: main (in /home/p) ==137== ==137== Invalid write of size 8 ==137== at 0x4C35E54: memset (vg_replace_strmem.c:1251) ==137== by 0x400873: main (in /home/p) ==137== Address 0x54281e0 is 60,768 bytes inside a block of size 65,536 alloc'd ==137== at 0x4C2EE0B: malloc (vg_replace_malloc.c:299) ==137== by 0x4007DF: main (in /home/p) ==137== ==137== Invalid write of size 8 ==137== at 0x4C35E57: memset (vg_replace_strmem.c:1251) ==137== by 0x400873: main (in /home/p) ==137== Address 0x54281e8 is 60,776 bytes inside a block of size 65,536 alloc'd ==137== at 0x4C2EE0B: malloc (vg_replace_malloc.c:299) ==137== by 0x4007DF: main (in /home/p) ==137== ==137== Invalid write of size 8 ==137== at 0x4C35E5B: memset (vg_replace_strmem.c:1251) ==137== by 0x400873: main (in /home/p) ==137== Address 0x54281f0 is 60,784 bytes inside a block of size 65,536 alloc'd ==137== at 0x4C2EE0B: malloc (vg_replace_malloc.c:299) ==137== by 0x4007DF: main (in /home/p) ==137== done ==137== ==137== HEAP SUMMARY: ==137== in use at exit: 0 bytes in 0 blocks ==137== total heap usage: 3 allocs, 3 frees, 66,832 bytes allocated ==137== ==137== All heap blocks were freed -- no leaks are possible ==137== ==137== For counts of detected and suppressed errors, rerun with: -v ==137== ERROR SUMMARY: 37 errors from 4 contexts (suppressed: 0 from 0) [root@4fe07db648b4 home]# valgrind --version valgrind-3.14.0 On Wed, Dec 12, 2018 at 9:36 PM John Reiser <jr...@bi...> wrote: > > - I have reproduced this on valgrind 3.10 and 3.13 > > It works correctly (no complaints) on x86_64 under valgrind 3.14 as > distributed > by Fedora 28 in valgrind-3.14.0-1.fc28.x86_64.rpm . > > $ rpm -q valgrind > valgrind-3.14.0-1.fc28.x86_64 > $ type valgrind > valgrind is hashed (/usr/bin/valgrind) > $ valgrind --version > valgrind-3.14.0 > $ gcc -g -O pthread_test.c -lpthread > $ valgrind ./a.out > ==18824== Memcheck, a memory error detector > ==18824== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. > ==18824== Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright > info > ==18824== Command: ./a.out > ==18824== > start > thread created > thread running > done > ==18824== > ==18824== HEAP SUMMARY: > ==18824== in use at exit: 0 bytes in 0 blocks > ==18824== total heap usage: 3 allocs, 3 frees, 66,832 bytes allocated > ==18824== > ==18824== All heap blocks were freed -- no leaks are possible > ==18824== > ==18824== For counts of detected and suppressed errors, rerun with: -v > ==18824== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) > $ > > > _______________________________________________ > Valgrind-users mailing list > Val...@li... > https://lists.sourceforge.net/lists/listinfo/valgrind-users > |
|
From: Chris T. <chr...@gm...> - 2018-12-13 06:53:54
|
Thank you both for your insights, they are both helpful and much
appreciated.
I think Julian is correct, valgrind doesn't know how to deal with this user
allocated heap - and it isn't OK with the memset() past the end of the
stack pointer. Using the memcheck.h API to mark it as undefined will
probably work for me.
Based on John's comments, I noticed he was specifying optimization level 1
when compiling ("-O"). I had been using the default ("-O0"). For some
reason, valgrind produces errors on this code with -O0, but not with any
other settings (-O1, -O2, -O3, -Os, -Ofast). I wonder if the optimizations
make it harder for valgrind to detect and mark memory beyond the stack
pointer as no-access? Regardless, using anything other than -O0 results in
clean output in my test case, so I have a second viable workaround. Thank
you very much for your help!
On Wed, Dec 12, 2018 at 10:43 PM Julian Seward <js...@ac...> wrote:
> On 13/12/2018 01:07, Chris Teague wrote:
>
> > 4. Clear out the threads stack via memset() to ensure I don't leave any
> > interesting data in the heap.
>
> If I had to guess, I'd say it was this. When the stack pointer moves up
> (to deallocate stuff stored on the stack), Memcheck marks the just-freed
> area as no-access, so it can detect mistaken attempts to access there
> later. So I imagine it has done this with your stack too. Result is that
> when you memset-zero your stack, you got the complaint you got.
>
> If you really want to do that, you can #include <valgrind/memcheck.h> and
> then use VALGRIND_MAKE_MEMORY_UNDEFINED(stack, length of stack) so as to
> mark it accessible but containing garbage, before you zero it out.
>
> J
>
|
|
From: Julian S. <js...@ac...> - 2018-12-13 08:08:28
|
On 13/12/2018 07:53, Chris Teague wrote:
> For some
> reason, valgrind produces errors on this code with -O0, but not with any
> other settings (-O1, -O2, -O3, -Os, -Ofast).
It might just be the case that gcc is clever enough to know that the memset
has no visible effect on the program state, since you're just about to free
the memory. So it simply removes the memset:
memset(p_stack, 'A', stack_size); <-- redundant
free(p_stack);
Gcc version 6 acquired a new optimisation, "lifetime dead-store elimination",
which is suspiciously similar to the above scenario. So it might be that.
But the only way to know for sure is to look at the generated machine code.
(Maybe try with -O -fno-lifetime-dse ?)
J
|
|
From: Julian S. <js...@ac...> - 2018-12-13 07:02:14
|
On 13/12/2018 01:07, Chris Teague wrote: > 4. Clear out the threads stack via memset() to ensure I don't leave any > interesting data in the heap. If I had to guess, I'd say it was this. When the stack pointer moves up (to deallocate stuff stored on the stack), Memcheck marks the just-freed area as no-access, so it can detect mistaken attempts to access there later. So I imagine it has done this with your stack too. Result is that when you memset-zero your stack, you got the complaint you got. If you really want to do that, you can #include <valgrind/memcheck.h> and then use VALGRIND_MAKE_MEMORY_UNDEFINED(stack, length of stack) so as to mark it accessible but containing garbage, before you zero it out. J |