2016-06-30 6 views
2

Все, я не знаю, какой предел я ударил, или это проблема с valgrind, libc или me, но мне нужно знать, если это воспроизводимо, и если да, то где эта проблема. Я отложил проблему до MCVE, выпускаемого на двух моих коробках AMD. В принципе, я динамически выделяю указатели struct dirent *, а затем выделяю struct dirent для каждого успешного readdir. valgrind не имеет жалобы на 1017, но затем на номер 1018, я получаю сообщение об ошибке invalid read (не связано с перераспределением).Valgrind 1017 calloc/memcopy struct dirent OK, 1018th - недействительный читать

==9881== Invalid read of size 8 
==9881== at 0x4C2F316: [email protected]@GLIBC_2.14 (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) 
==9881== by 0x40098E: main (readdir_mcve.c:35) 
==9881== Address 0x51df070 is 0 bytes after a block of size 32,816 alloc'd 
==9881== at 0x4C2ABD0: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) 
==9881== by 0x4EE93E3: __alloc_dir (in /usr/lib/libc-2.23.so) 
==9881== by 0x4EE94D2: opendir_tail (in /usr/lib/libc-2.23.so) 
==9881== by 0x400802: main (readdir_mcve.c:9) 

(block of size 32,816 выглядит любопытно, но я не нашел никакой помощи разбив его)

код принимает имя каталога, чтобы открыть в качестве первого аргумента, а затем предел файлов для чтения как второй (по умолчанию 1000):

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <sys/types.h> 
#include <dirent.h> 

int main (int argc, char **argv) { 

    DIR *dp = opendir (argc > 1 ? argv[1] : "."); /* open directory (. default) */ 
    struct dirent *de = NULL, **dlist = NULL;  /* ptr and ptr2ptr to dirent */ 
    size_t nptrs = argc > 2 ? (size_t)strtoul (argv[2], NULL, 10) : 1000, 
      i = 0, idx = 0;      /* index, allocation counter */ 

    if (!dp) { 
     fprintf (stderr, "error: opendir failed.\n"); 
     return 1; 
    } 

    /* allocate nptrs dirent pointers */ 
    if (!(dlist = calloc (nptrs, sizeof *dlist))) { 
     fprintf (stderr, "error: virtual memory exhausted - dlist\n"); 
     return 1; 
    } 

    while ((de = readdir (dp))) { 

     /* skip dot files */ 
     if (!strcmp (de->d_name, ".") || !strcmp (de->d_name, "..")) 
      continue; 

     if (!(dlist[idx] = calloc (1, sizeof **dlist))) { /* alloc dirent */ 
      fprintf (stderr, "error: dlist memory allocation failed\n"); 
      return 1; 
     } 
     memcpy (dlist[idx++], de, sizeof *de); /* copy de to dlist[idx] */ 

     if (idx == nptrs) /* post-check/realloc, insures sentinel NULL */ 
      break; 
    } 
    closedir (dp); 

    for (i = 0; i < idx; i++) { 
     printf (" file[%3zu] : %s\n", i, dlist[i]->d_name); 
     free (dlist[i]); 
    } 
    free (dlist); 

    return 0; 
} 

Вы можете создать простой тестовый каталог с:

$ mkdir readdir_tst 
$ for i in {1..1024}; do 
    printf -v fname "file%04d" "$i" 
    touch "readdir_tst/$fname" 
    done 

Тогда все хорошо читает 1014 имена файлов (1017 распределения):

$ valgrind ./bin/readdir_mcve readdir_tst 1014 > /dev/null 
==9880== Memcheck, a memory error detector 
==9880== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al. 
==9880== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info 
==9880== Command: ./bin/readdir_mcve readdir_tst 1014 
==9880== 
==9880== 
==9880== HEAP SUMMARY: 
==9880==  in use at exit: 0 bytes in 0 blocks 
==9880== total heap usage: 1,017 allocs, 1,017 frees, 328,944 bytes allocated 
==9880== 
==9880== All heap blocks were freed -- no leaks are possible 
==9880== 
==9880== For counts of detected and suppressed errors, rerun with: -v 
==9880== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) 

Но файл 1015 (распределение 1018) Я ударил __alloc_dir вопрос и Valgrind бросает Invalid read of size 8 ... is 0 bytes after a block of size 32,816 alloc'd:

$ valgrind ./bin/readdir_mcve readdir_tst 1015 > /dev/null 
==9881== Memcheck, a memory error detector 
==9881== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al. 
==9881== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info 
==9881== Command: ./bin/readdir_mcve readdir_tst 1015 
==9881== 
==9881== Invalid read of size 8 
==9881== at 0x4C2F316: [email protected]@GLIBC_2.14 (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) 
==9881== by 0x40098E: main (readdir_mcve.c:35) 
==9881== Address 0x51df070 is 0 bytes after a block of size 32,816 alloc'd 
==9881== at 0x4C2ABD0: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) 
==9881== by 0x4EE93E3: __alloc_dir (in /usr/lib/libc-2.23.so) 
==9881== by 0x4EE94D2: opendir_tail (in /usr/lib/libc-2.23.so) 
==9881== by 0x400802: main (readdir_mcve.c:9) 
==9881== 
==9881== 
==9881== HEAP SUMMARY: 
==9881==  in use at exit: 0 bytes in 0 blocks 
==9881== total heap usage: 1,018 allocs, 1,018 frees, 329,232 bytes allocated 
==9881== 
==9881== All heap blocks were freed -- no leaks are possible 
==9881== 
==9881== For counts of detected and suppressed errors, rerun with: -v 
==9881== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0) 

Код продолжает читать и печатать все записи в каталоге просто отлично, но это ошибка valgrind, которая меня озадачила. У меня было это перераспределение вместо того, чтобы звонить break при достижении предела распределения, и обрабатывает файлы 4000+ в /usr/bin без каких-либо проблем, кроме ошибки valgrind. (который был лишен как не относящийся к MCVE). Самое близкое, что я нашел на SO, - Valgrind malloc leaks, но здесь это неприменимо. Может ли кто-нибудь еще воспроизвести это, и если да, то это valgrind, libc или me?

примечание: Я получаю те же результаты с libc-2.18.so.


GNU Libc dirent.h предлагает немного больше информации

После того, как в правильном направлении по ответу и продолжает поиск немного, кажется, есть несколько способов LibC может определить длину d_name. Это будет зависеть от различных определений, доступных компилятору.В dirent.h объясняет:

46 /* This file defines `struct dirent'. 
47 
48 It defines the macro `_DIRENT_HAVE_D_NAMLEN' iff there is a `d_namlen' 
49 member that gives the length of `d_name'. 
... 
59 */ 
... 
67 /* These macros extract size information from a `struct dirent *'. 
68 They may evaluate their argument multiple times, so it must not 
69 have side effects. Each of these may involve a relatively costly 
70 call to `strlen' on some systems, so these values should be cached. 
71 
72 _D_EXACT_NAMLEN (DP) returns the length of DP->d_name, not including 
73 its terminating null character. 
74 
75 _D_ALLOC_NAMLEN (DP) returns a size at least (_D_EXACT_NAMLEN (DP) + 1); 
76 that is, the allocation size needed to hold the DP->d_name string. 
77 Use this macro when you don't need the exact length, just an upper bound. 
78 This macro is less likely to require calling `strlen' than _D_EXACT_NAMLEN. 
79 */ 
80 
81 #ifdef _DIRENT_HAVE_D_NAMLEN 
82 # define _D_EXACT_NAMLEN(d) ((d)->d_namlen) 
83 # define _D_ALLOC_NAMLEN(d) (_D_EXACT_NAMLEN (d) + 1) 
84 #else 
85 # define _D_EXACT_NAMLEN(d) (strlen ((d)->d_name)) 
86 # ifdef _DIRENT_HAVE_D_RECLEN 
87 # define _D_ALLOC_NAMLEN(d) (((char *) (d) + (d)->d_reclen) - &(d)->d_name[0]) 
88 # else 
89 # define _D_ALLOC_NAMLEN(d) (sizeof (d)->d_name > 1 ? sizeof (d)->d_name : \ 
90        _D_EXACT_NAMLEN (d) + 1) 
91 # endif 
92 #endif 
... 

Хотя существует целый ряд различных компиляции путей это может принимать на основе различных определяет, тот, который является наиболее читаемым происходит, если __USE_XOPEN2K8 установлено:

221 #ifdef __USE_XOPEN2K8 
222 ... 
230 # ifdef __USE_MISC 
231 # ifndef MAXNAMLEN 
232 /* Get the definitions of the POSIX.1 limits. */ 
233 # include <bits/posix1_lim.h> 
234 
235 /* `MAXNAMLEN' is the BSD name for what POSIX calls `NAME_MAX'. */ 
236 # ifdef NAME_MAX 
237 # define MAXNAMLEN NAME_MAX 
238 # else 
239 # define MAXNAMLEN 255 
240 # endif 
241 # endif 
242 # endif 

Таким образом, в этот случай, d_name является либо NAME_MAX, либо 255 в зависимости от определения NAME_MAX (и таким образом макросом _D_ALLOC_NAMLEN (DP)). Спасибо, что направил меня в правильном направлении. Я не знаю, можем ли мы когда-либо узнать точный ответ о том, почему 1017struct dirent распределения происходят без проблем и почему valgrind начинает жаловаться на номер 1018, но, по крайней мере, теперь мы понимаем, где возникает источник проблемы и почему копирование struct dirent с memcpy могут создавать проблемы.

+0

Какие аргументы вы передаете программе? – 2501

+0

Теперь это то, что мне нужно будет исследовать. Я только открываю один каталог, но я вижу проблему, если каждый последующий 'readdir' держит ручку открытой? Я не знаю ответа на этот вопрос и должен буду копать дальше. –

+0

@ 2501 './bin/readdir_mcve readdir_tst 1014' (works)' ./bin/readdir_mcve readdir_tst 1015' (первая ошибка valgrind) –

ответ

5

Вы не можете скопировать strucft dirent, это похоже на the manual page, и код не синхронизирован.

Вот the current declaration:

struct dirent 
    { 
#ifndef __USE_FILE_OFFSET64 
    __ino_t d_ino;  /* File serial number. */ 
#else 
    __ino64_t d_ino; 
#endif 
    unsigned short int d_reclen; /* Length of the whole `struct dirent'. */ 
    unsigned char d_type; /* File type, possibly unknown. */ 
    unsigned char d_namlen; /* Length of the file name. */ 

    /* Only this member is in the POSIX standard. */ 
    char d_name[1];  /* File name (actually longer). */ 
    }; 

Очевидно, так d_name объявлен как [1], вы не получите нужного размера с помощью sizeof. Вам нужно сделать больше умного хранилища, т. Е. strdup() имена или что-то (если вас интересуют только имена).

Я не уверен на 100%, почему это приводит к поломке, но я уверен, вы видите какой-то UB (обратите внимание, что вы умираете при чтении, если вы достигли printf() на ваших скопированных строках, которые вызовут UB).

+0

Конечно, это взлома структуры ... Upvoted! – 2501

+0

Вот что я искал. На странице «man» отображается 'd_name [256]', и я полагался на 'sizeof (struct dirent)', который возвращал '280'. Спасибо, что помогли решить эту тайну. И да, у меня есть 'dlist' как' char ** 'в другом коде, который работал, но я собирался выяснить, почему' dirent' выбрасывает erorr - или умеет пытаться ':)' –

+0

280? Это ... больше, чем я ожидал от декларации. Странный. – unwind