Dlaczego otrzymuję błąd asercji C malloc?

85

Implementuję algorytm wielomianowy dziel i zwyciężaj, więc mogę porównać go z implementacją OpenCL, ale nie mogę zabrać się mallocdo pracy. Kiedy uruchamiam program, przydziela on kilka rzeczy, sprawdza niektóre rzeczy, a następnie wysyła size/2do algorytmu. Następnie, kiedy mallocponownie trafiam na linię, wypluwa to:

malloc.c:3096: sYSMALLOc: Assertion `(old_top == (((mbinptr) (((char *) &((av)->bins[((1) - 1) * 2])) - __builtin_offsetof (struct malloc_chunk, fd)))) && old_size == 0) || ((unsigned long) (old_size) >= (unsigned long)((((__builtin_offsetof (struct malloc_chunk, fd_nextsize))+((2 * (sizeof(size_t))) - 1)) & ~((2 * (sizeof(size_t))) - 1))) && ((old_top)->size & 0x1) && ((unsigned long)old_end & pagemask) == 0)' failed.
Aborted

Przedmiotowa linia to:

int *mult(int size, int *a, int *b) {
    int *out,i, j, *tmp1, *tmp2, *tmp3, *tmpa1, *tmpa2, *tmpb1, *tmpb2,d, *res1, *res2;
    fprintf(stdout, "size: %d\n", size);

    out = (int *)malloc(sizeof(int) * size * 2);
}

Sprawdziłem rozmiar za pomocą a fprintfi jest to dodatnia liczba całkowita (zwykle w tym momencie 50). Próbowałem również zadzwonić mallocze zwykłego numeru i nadal otrzymuję błąd. Zastanawiam się, co się dzieje, i nic z Google, które znalazłem do tej pory, nie jest pomocne.

Jakieś pomysły, co się dzieje? Próbuję wymyślić, jak skompilować nowsze GCC na wypadek, gdyby był to błąd kompilatora, ale naprawdę w to wątpię.

Chris
źródło
Podejrzewam, że problem dotyczy w rzeczywistości linii wcześniejszej. Może podwójnie za darmo?
Mitch Wheat
3. linia programu: int * mult (int size, int * a, int * b) {int * out, i, j, * tmp1, * tmp2, * tmp3, * tmpa1, * tmpa2, * tmpb1, * tmpb2 , d, * res1, * res2; fprintf (stdout, "rozmiar:% d \ n", rozmiar); out = (int *) malloc (sizeof (int) * size * 2);
Chris,

Odpowiedzi:

98

99,9% prawdopodobieństwa, że ​​masz uszkodzoną pamięć (przepełniony lub niedostateczny bufor, zapisany do wskaźnika po jego zwolnieniu, dwukrotnie wywołany wolny na tym samym wskaźniku itp.)

Uruchom swój kod pod Valgrind, aby zobaczyć, gdzie twój program zrobił coś nieprawidłowego.

R Samuel Klatchko
źródło
1
naprawiony. Valgrind zdecydowanie pomógł. Napisałem mój stary kod Matlaba źle i miałem pętlę for, która iterowała po j, a następnie w niej wykonałem j ++, które najczęściej nadpisywało tablicę, na której pisał, i w jakiś sposób spowodowało awarię malloc. dzięki za pomoc!
Chris,
Valgrind był tylko narzędziem, którego potrzebowałem, aby dowiedzieć się, co się dzieje, kiedy otrzymałem ten błąd. Dzięki, że o tym wspomniałeś.
alexwells
77

Aby lepiej zrozumieć, dlaczego tak się dzieje, chciałbym nieco rozwinąć odpowiedź @ r-samuel-klatchko.

Kiedy dzwonisz malloc, to, co się naprawdę dzieje, jest nieco bardziej skomplikowane niż zwykłe udostępnianie kawałka pamięci do zabawy. Pod maską mallocprzechowuje również pewne informacje porządkowe dotyczące pamięci, którą Ci przekazał (co najważniejsze, jej rozmiaru), dzięki czemu kiedy dzwonisz free, wie takie rzeczy, jak ilość pamięci do zwolnienia. Te informacje są zwykle przechowywane tuż przed zwróceniem lokalizacji pamięci przez malloc. Bardziej wyczerpujące informacje można znaleźć w Internecie ™ , ale (bardzo) podstawowa idea jest taka:

+------+-------------------------------------------------+
+ size |                  malloc'd memory                +
+------+-------------------------------------------------+
       ^-- location in pointer returned by malloc

Opierając się na tym (i znacznie upraszczając), kiedy dzwonisz malloc , musi uzyskać wskaźnik do następnej dostępnej części pamięci. Jednym z bardzo prostych sposobów jest przyjrzenie się poprzedniej części pamięci, którą oddał, i przesunięcie sizebajtów dalej w dół (lub w górę) pamięci. Dzięki tej realizacji, możesz skończyć z pamięci poszukuje czegoś takiego po alokacji p1, p2oraz p3:

+------+----------------+------+--------------------+------+----------+
+ size |                | size |                    | size |          +
+------+----------------+------+--------------------+------+----------+
       ^- p1                   ^- p2                       ^- p3

Więc co powoduje twój błąd?

Cóż, wyobraź sobie, że twój kod błędnie zapisuje więcej niż ilość przydzielonej pamięci (albo dlatego, że przydzieliłeś mniej niż potrzebujesz, tak jak był twój problem, albo ponieważ gdzieś w kodzie używasz niewłaściwych warunków brzegowych). Powiedz, że Twój kod zapisuje tak dużo danychp2 , że zaczyna nadpisywanie co jest w p3„s sizedziedzinie. Kiedy teraz wykonasz następne wywołanie malloc, sprawdzi ostatnią lokalizację pamięci, którą zwrócił, spojrzy na pole rozmiaru, przejdzie do, p3 + sizea następnie rozpocznie alokację pamięci z tego miejsca. Ponieważ jednak kod został nadpisany size, ta lokalizacja pamięci nie jest już po poprzednio przydzielonej pamięci.

Nie trzeba dodawać, że może to siać spustoszenie! Dlatego też implementatorzy mallocwprowadzili szereg „asercji” lub sprawdzeń, które próbują wykonać kilka sprawdzeń poczytalności, aby złapać to (i inne problemy), jeśli mają się wydarzyć. W twoim konkretnym przypadku te twierdzenia są naruszane, a zatem mallocprzerywają, informując cię, że twój kod miał zrobić coś, czego naprawdę nie powinien robić.

Jak już wspomniano, jest to znaczne uproszczenie, ale wystarczy to zilustrować. Implementacja glibc mallocobejmuje ponad 5 tys. Linii i przeprowadzono wiele badań nad tym, jak zbudować dobre mechanizmy dynamicznej alokacji pamięci, więc uwzględnienie tego wszystkiego w odpowiedzi SO nie jest możliwe. Miejmy nadzieję, że dało ci to trochę poglądu na to, co naprawdę powoduje problem!

Jon Gjengset
źródło
14

Moje alternatywne rozwiązanie do korzystania z Valgrind:

Jestem bardzo szczęśliwy, ponieważ właśnie pomogłem mojemu przyjacielowi zdebugować program. Jego program miał dokładnie ten problem ( malloc()powodujący przerwanie), z tym samym komunikatem o błędzie z GDB.

Skompilowałem jego program przy użyciu Address Sanitizer z

gcc -Wall -g3 -fsanitize=address -o new new.c
              ^^^^^^^^^^^^^^^^^^

A potem uciekł gdb new. Kiedy program zostanie zakończony przez SIGABRTspowodowany w kolejnym malloc(), drukowanych jest wiele przydatnych informacji:

=================================================================
==407==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6060000000b4 at pc 0x7ffffe49ed1a bp 0x7ffffffedc20 sp 0x7ffffffed3c8
WRITE of size 104 at 0x6060000000b4 thread T0
    #0 0x7ffffe49ed19  (/usr/lib/x86_64-linux-gnu/libasan.so.4+0x5ed19)
    #1 0x8001dab in CreatHT2 /home/wsl/Desktop/hash/new.c:59
    #2 0x80031cf in main /home/wsl/Desktop/hash/new.c:209
    #3 0x7ffffe061b96 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21b96)
    #4 0x8001679 in _start (/mnt/d/Desktop/hash/new+0x1679)

0x6060000000b4 is located 0 bytes to the right of 52-byte region [0x606000000080,0x6060000000b4)
allocated by thread T0 here:
    #0 0x7ffffe51eb50 in __interceptor_malloc (/usr/lib/x86_64-linux-gnu/libasan.so.4+0xdeb50)
    #1 0x8001d56 in CreatHT2 /home/wsl/Desktop/hash/new.c:55
    #2 0x80031cf in main /home/wsl/Desktop/hash/new.c:209
    #3 0x7ffffe061b96 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21b96)

Przyjrzyjmy się wynikowi, zwłaszcza śladowi stosu:

Pierwsza część mówi, że w miejscu jest nieprawidłowa operacja zapisu new.c:59. Ta linia brzmi

memset(len,0,sizeof(int*)*p);
             ^^^^^^^^^^^^

Druga część mówi, że pamięć, w której nastąpił zły zapis, jest tworzona w new.c:55. Ta linia brzmi

if(!(len=(int*)malloc(sizeof(int)*p))){
                      ^^^^^^^^^^^

Otóż ​​to. Zajęło mi tylko mniej niż pół minuty, aby zlokalizować błąd, który mylił mojego przyjaciela przez kilka godzin. Udało mu się zlokalizować awarię, ale to kolejnamalloc() wywołanie, które się nie powiodło, bez możliwości wykrycia tego błędu w poprzednim kodzie.

Podsumowując: wypróbuj -fsanitize=addressGCC lub Clang. Może być bardzo pomocny podczas debugowania problemów z pamięcią.

iBug
źródło
1
Właśnie uratowałeś mi życie.
Nate Symer
2

Prawdopodobnie gdzieś przekraczasz przydzieloną pamięć. to bazowy SW nie odbiera go, dopóki nie zadzwonisz do malloc

Może być pobita wartość strażnika, która została złapana przez malloc.

edit ... dodał to do pomocy przy sprawdzaniu granic

http://www.lrde.epita.fr/~akim/ccmp/doc/bounds-checking.html

pbernatchez
źródło
2

Otrzymałem następującą wiadomość, podobną do Twojej:

    program: malloc.c: 2372: sysmalloc: Assertion `(old_top == (((mbinptr) (((char *) & ((av) -> bins [((1) - 1) * 2])) - __builtin_offsetof (struct malloc_chunk, fd)))) && old_size == 0) || ((unsigned long) (old_size)> = (unsigned long) ((((__ builtin_offsetof (struct malloc_chunk, fd_nextsize)) + ((2 * (sizeof (size_t))) - 1)) & ~ ((2 * (sizeof) (size_t))) - 1))) && ((old_top) -> size & 0x1) && ((unsigned long) old_end & pagemask) == 0) 'nie powiodło się.

Podczas korzystania z malloc popełniłem błąd przed wywołaniem metody. Błędnie nadpisano znak mnożenia „*” znakiem „+” podczas aktualizacji współczynnika po operatorze sizeof () - przy dodawaniu pola do tablicy bez znaku.

Oto kod odpowiedzialny za błąd w moim przypadku:

    UCHAR * b = (UCHAR *) malloc (sizeof (UCHAR) +5);
    b [INTBITS] = (jakieś obliczenia);
    b [BUFSPC] = (jakieś obliczenia);
    b [BUFOVR] = (jakieś obliczenia);
    b [BUFMEM] = (jakieś obliczenia);
    b [MATCHBITS] = (jakieś obliczenia);

Później w innej metodzie ponownie użyłem malloc i wyświetlił się komunikat o błędzie pokazany powyżej. Rozmowa była (dość prosta):

    UCHAR * b = (UCHAR *) malloc (sizeof (UCHAR) * 50);

Pomyśl o użyciu znaku '+' - przy pierwszym wywołaniu, które doprowadziło do błędnego rachunku w połączeniu z natychmiastową inicjalizacją tablicy po (nadpisaniu pamięci, która nie została przydzielona do tablicy), wprowadziło pewne zamieszanie do mapy pamięci malloc. Dlatego drugie połączenie poszło nie tak.

Michael Grieswald
źródło
0

Otrzymaliśmy ten błąd, ponieważ zapomnieliśmy pomnożyć przez sizeof (int). Zwróć uwagę, że argument funkcji malloc (..) to liczba bajtów, a nie liczba słów maszynowych czy cokolwiek innego.

Phob
źródło
0

Mam ten sam problem, użyłem malloc nad n ponownie w pętli do dodawania nowych danych ciągu char *. napotkałem ten sam problem, ale po zwolnieniu przydzielonej pamięci void free()problem został rozwiązany

namila007
źródło
-2

Portowałem jedną aplikację z Visual C na gcc przez Linuksa i miałem ten sam problem

malloc.c: 3096: sYSMALLOc: Asercja przy użyciu gcc na UBUNTU 11.

Przeniosłem ten sam kod do dystrybucji Suse (na innym komputerze) i nie mam żadnego problemu.

Podejrzewam, że problemy nie występują w naszych programach, ale we własnej bibliotece libc.

JMH
źródło