2

我将一个更大的程序提炼成底部显示的代码。在valgrind中运行这个程序最终会报这个错误:

==7234== 大小为 4 的读取无效
==7234== 在 0x34A7275FC8:_IO_file_write@@GLIBC_2.2.5(在 /usr/lib64/libc-2.15.so 中)
==7234== 由 0x34A7275EA1:new_do_write(在 /usr/lib64/libc-2.15.so 中)
==7234== 0x34A7276D44:_IO_do_write@@GLIBC_2.2.5(在 /usr/lib64/libc-2.15.so 中)
==7234== 由 0x34A7278DB6:_IO_flush_all_lockp(在 /usr/lib64/libc-2.15.so 中)
==7234== 由 0x34A7278F07:_IO_cleanup(在 /usr/lib64/libc-2.15.so 中)
==7234== 由 0x34A7238BBF:__run_exit_handlers(在 /usr/lib64/libc-2.15.so 中)
==7234== by 0x34A7238BF4:退出(在 /usr/lib64/libc-2.15.so 中)
==7234== by 0x34A722173B: (在 main 下面) (在 /usr/lib64/libc-2.15.so)
==7234== 地址 0x542f2e0 是大小为 568 的块内的 0 个字节 free'd
==7234== 在 0x4A079AE: 空闲 (vg_replace_malloc.c:427)
==7234== by 0x34A726B11C: fclose@@GLIBC_2.2.5 (在/usr/lib64/libc-2.15.so)
==7234== by 0x40087C: writer (tc:22)
==7234== by 0x34A7607D13: start_thread (在/usr/lib64/libpthread-2.15.so)
==7234== by 0x34A72F167C:克隆(在 /usr/lib64/libc-2.15.so 中)


从上面的输出来看,这似乎是正在发生的事情:

  • main() 返回,并开始运行退出处理程序以关闭所有 FILE*
  • writer() 线程,仍在运行,唤醒,关闭 FILE*
  • 退出处理程序尝试访问已关闭的 FILE*,它现在是无效/free()'d

据我所知,测试程序没有做任何未定义的事情,但我很乐意在这方面犯错。

Valgrind 与各种功能挂钩,因此它可能是 valgrind 错误而不是 glibc。

  • 这是一个 glibc 错误吗?
  • 或者它是一个 valgrind 错误?

  • 关于如何确定它是 valgrind 还是 glibc 的任何想法?

tc:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void *test(void *arg)
{
    return NULL;
}
void *writer(void *arg)
{
    for(;;) {
        char a[100];
        FILE *f = fopen("out", "w");

        if(f == NULL)
           abort();

        fputs("Test", f);

        if(fgets(a, 100, stdin))
            fputs(a, f);
        fclose(f);  //line 22
    }

    return NULL;
}

int main(int argc, char *argv[])
{
    pthread_t tid1,tid2;


    pthread_create(&tid1, NULL, writer, NULL);
    pthread_create(&tid2, NULL, test, NULL);
    pthread_join(tid2, NULL);
    //pthread_join(tid1, NULL); //no bug if we wait for writer()
    return 0;
}
//compile: gcc t.c -g -pthread

可能需要几分钟才能从 valgrind 触发错误,其中:

while [ true ]; do 
  echo test |valgrind --error-exitcode=2 ./a.out  || break  
done  

环境:Fedora 17、glibc-2.15、gcc-4.7.0-5、内核 3.5.3-1.fc17.x86_64、valgrind-3.7.0-4

4

1 回答 1

4

你有一个竞争条件。您有一个调用 的线程,该线程exit被记录为关闭所有打开的 stdio 流。然后,您有另一个线程,可能在exit关闭它之后访问这样的流。a 关闭后你不能访问FILE*它——它被允许指向垃圾。

如果一个线程做了一些使调用exit不安全的事情,你必须确保你不调用exit. 真的就是这么简单。

于 2012-09-23T04:38:46.743 回答