Cómo localizar un error "doble libre o corrupto"

92

Cuando ejecuto mi programa (C ++), se bloquea con este error.

* glibc detectado * ./load: doble libre o corrupción (! prev): 0x0000000000c6ed50 ***

¿Cómo puedo localizar el error?

Intenté usar std::coutdeclaraciones print ( ), sin éxito. ¿Podría gdbhacer esto más fácil?

neuromante
fuente
5
Me pregunto por qué todo el mundo sugiere NULLpunteros (que enmascaran errores que de otra manera se detectan, como muestra muy bien esta pregunta), pero nadie sugiere simplemente no hacer la gestión manual de memoria en absoluto, lo cual es muy posible en C ++. No he escrito deleteen años. (Y, sí, mi código es crítico para el rendimiento. De lo contrario, no se habría escrito en C ++.)
sbi
2
@sbi: Rara vez se detecta la corrupción en masa y similares, al menos no donde ocurren. NULLLos punteros pueden hacer que su programa se bloquee antes.
Hasturkun
@Hasturkun: Estoy totalmente en desacuerdo. Un incentivo importante para los NULLpunteros es evitar que delete ptr;explote un segundo , lo que enmascara un error, porque ese segundo deletenunca debería haber sucedido. (También se usa para verificar si un puntero sigue apuntando a un objeto válido. Pero eso solo plantea la pregunta de por qué tiene un puntero en el alcance que no tiene un objeto al que apuntar).
sbi

Respuestas:

64

Si está usando glibc, puede establecer la MALLOC_CHECK_variable de entorno en 2, esto hará que glibc use una versión tolerante a errores de malloc, lo que hará que su programa se anule en el punto donde se realiza el doble libre.

Puede configurar esto desde gdb usando el set environment MALLOC_CHECK_ 2comando antes de ejecutar su programa; el programa debe abortar, con la free()llamada visible en el backtrace.

consulte la páginamalloc() del manual para obtener más información

Hasturkun
fuente
2
La configuración MALLOC_CHECK_2realmente solucionó mi problema de doble libre (aunque no se está solucionando si está solo en modo de depuración)
puk
4
@puk Tengo el mismo problema, configurar MALLOC_CHECK_ en 2 evita mi problema de doble libre. ¿Qué otras opciones hay para inyectar menos código para reproducir el problema y proporcionar un seguimiento?
Wei Zhong
También tenlo donde configurar MALLOC_CHECK_ evita el problema. Compañeros comentaristas / cualquiera ... ¿Descubriste una manera diferente de exhibir el problema?
pellucidcoder
"Cuando MALLOC_CHECK_ se establece en un valor distinto de cero, se usa una implementación especial (menos eficiente) que está diseñada para ser tolerante contra errores simples, como llamadas dobles de free con el mismo argumento o desbordamientos de un solo byte (desactivado -por-uno, errores) ". gnu.org/software/libc/manual/html_node/… Entonces parece que MALLOC_CHECK_ solo se usa para evitar errores simples de memoria, no para detectarlos.
pellucidcoder
En realidad .... support.microfocus.com/kb/doc.php?id=3113982 parece que configurar MALLOC_CHECK_ en 3 es lo más útil y se puede usar para detectar errores.
pellucidcoder
32

Hay al menos dos situaciones posibles:

  1. estás eliminando la misma entidad dos veces
  2. estás eliminando algo que no se asignó

Para el primero, sugiero encarecidamente anular todos los punteros eliminados.

Tienes tres opciones:

  1. sobrecargar nuevas y eliminar y realizar un seguimiento de las asignaciones
  2. sí, use gdb, luego obtendrá un seguimiento de su bloqueo, y eso probablemente será muy útil
  3. como se sugiere, use Valgrind, no es fácil de ingresar, pero le ahorrará miles de veces en el futuro ...
Kornel Kisielewicz
fuente
2. Causaría corrupción, pero no creo que este mensaje aparezca en general, ya que la verificación de cordura solo se realiza en el montón. Sin embargo, creo que es posible el desbordamiento del búfer de pila.
Matthew Flaschen
Buena. Es cierto que no logré hacer que el puntero sea NULL y me enfrenté a este error. ¡Lecciones aprendidas!
hrushi
26

Puede usar gdb, pero primero probaría Valgrind . Consulte la guía de inicio rápido .

Brevemente, Valgrind instrumenta su programa para que pueda detectar varios tipos de errores en el uso de memoria asignada dinámicamente, como liberaciones dobles y escrituras más allá del final de los bloques de memoria asignados (que pueden dañar el montón). Detecta e informa los errores tan pronto como ocurren , lo que le indica directamente la causa del problema.

Matthew Flaschen
fuente
1
@SMR, en este caso, la parte esencial de la respuesta es la página completa, grande y vinculada. Entonces, incluir solo el enlace en la respuesta está perfectamente bien. Algunas palabras sobre por qué el autor prefiere Valgrind sobre gdb y cómo abordaría el problema específico es en mi humilde opinión lo que realmente falta en la respuesta.
ndemou
20

Tres reglas básicas:

  1. Establecer puntero en NULLafter free
  2. Verifique NULLantes de liberar.
  3. Inicialice el puntero hacia NULLal principio.

La combinación de estos tres funciona bastante bien.

Jack
fuente
1
No soy un experto en C, pero normalmente puedo mantener la cabeza fuera del agua. ¿Por qué # 1? ¿Es solo para que su programa se bloquee por completo cuando intenta acceder a un puntero gratuito, y no solo un error silencioso?
Daniel Harms
1
@Precision: Sí, ese es el punto. Es una buena práctica: tener un puntero a la memoria borrada es un riesgo.
ere
10
Estrictamente hablando, creo que el número 2 es innecesario ya que la mayoría de los compiladores le permitirán intentar eliminar un puntero nulo sin que esto cause un problema. Estoy seguro de que alguien me corregirá si me equivoco. :)
Componente 10 de
11
@ Component10 Creo que el estándar C requiere liberar NULL para no hacer nada.
Demi
2
@Demetri: Sí, tienes razón "si el valor del operando de eliminar es el puntero nulo, la operación no tiene ningún efecto". (ISO / IEC 14882: 2003 (E) 5.3.5.2)
Componente 10 de
15

Puede utilizar valgrindpara depurarlo.

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

int main()
{
 char *x = malloc(100);
 free(x);
 free(x);
 return 0;
}

[sand@PS-CNTOS-64-S11 testbox]$ vim t1.c
[sand@PS-CNTOS-64-S11 testbox]$ cc -g t1.c -o t1
[sand@PS-CNTOS-64-S11 testbox]$ ./t1
*** glibc detected *** ./t1: double free or corruption (top): 0x00000000058f7010 ***
======= Backtrace: =========
/lib64/libc.so.6[0x3a3127245f]
/lib64/libc.so.6(cfree+0x4b)[0x3a312728bb]
./t1[0x400500]
/lib64/libc.so.6(__libc_start_main+0xf4)[0x3a3121d994]
./t1[0x400429]
======= Memory map: ========
00400000-00401000 r-xp 00000000 68:02 30246184                           /home/sand/testbox/t1
00600000-00601000 rw-p 00000000 68:02 30246184                           /home/sand/testbox/t1
058f7000-05918000 rw-p 058f7000 00:00 0                                  [heap]
3a30e00000-3a30e1c000 r-xp 00000000 68:03 5308733                        /lib64/ld-2.5.so
3a3101b000-3a3101c000 r--p 0001b000 68:03 5308733                        /lib64/ld-2.5.so
3a3101c000-3a3101d000 rw-p 0001c000 68:03 5308733                        /lib64/ld-2.5.so
3a31200000-3a3134e000 r-xp 00000000 68:03 5310248                        /lib64/libc-2.5.so
3a3134e000-3a3154e000 ---p 0014e000 68:03 5310248                        /lib64/libc-2.5.so
3a3154e000-3a31552000 r--p 0014e000 68:03 5310248                        /lib64/libc-2.5.so
3a31552000-3a31553000 rw-p 00152000 68:03 5310248                        /lib64/libc-2.5.so
3a31553000-3a31558000 rw-p 3a31553000 00:00 0
3a41c00000-3a41c0d000 r-xp 00000000 68:03 5310264                        /lib64/libgcc_s-4.1.2-20080825.so.1
3a41c0d000-3a41e0d000 ---p 0000d000 68:03 5310264                        /lib64/libgcc_s-4.1.2-20080825.so.1
3a41e0d000-3a41e0e000 rw-p 0000d000 68:03 5310264                        /lib64/libgcc_s-4.1.2-20080825.so.1
2b1912300000-2b1912302000 rw-p 2b1912300000 00:00 0
2b191231c000-2b191231d000 rw-p 2b191231c000 00:00 0
7ffffe214000-7ffffe229000 rw-p 7ffffffe9000 00:00 0                      [stack]
7ffffe2b0000-7ffffe2b4000 r-xp 7ffffe2b0000 00:00 0                      [vdso]
ffffffffff600000-ffffffffffe00000 ---p 00000000 00:00 0                  [vsyscall]
Aborted
[sand@PS-CNTOS-64-S11 testbox]$


[sand@PS-CNTOS-64-S11 testbox]$ vim t1.c
[sand@PS-CNTOS-64-S11 testbox]$ cc -g t1.c -o t1
[sand@PS-CNTOS-64-S11 testbox]$ valgrind --tool=memcheck ./t1
==20859== Memcheck, a memory error detector
==20859== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==20859== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
==20859== Command: ./t1
==20859==
==20859== Invalid free() / delete / delete[]
==20859==    at 0x4A05A31: free (vg_replace_malloc.c:325)
==20859==    by 0x4004FF: main (t1.c:8)
==20859==  Address 0x4c26040 is 0 bytes inside a block of size 100 free'd
==20859==    at 0x4A05A31: free (vg_replace_malloc.c:325)
==20859==    by 0x4004F6: main (t1.c:7)
==20859==
==20859==
==20859== HEAP SUMMARY:
==20859==     in use at exit: 0 bytes in 0 blocks
==20859==   total heap usage: 1 allocs, 2 frees, 100 bytes allocated
==20859==
==20859== All heap blocks were freed -- no leaks are possible
==20859==
==20859== For counts of detected and suppressed errors, rerun with: -v
==20859== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 4 from 4)
[sand@PS-CNTOS-64-S11 testbox]$


[sand@PS-CNTOS-64-S11 testbox]$ valgrind --tool=memcheck --leak-check=full ./t1
==20899== Memcheck, a memory error detector
==20899== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==20899== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
==20899== Command: ./t1
==20899==
==20899== Invalid free() / delete / delete[]
==20899==    at 0x4A05A31: free (vg_replace_malloc.c:325)
==20899==    by 0x4004FF: main (t1.c:8)
==20899==  Address 0x4c26040 is 0 bytes inside a block of size 100 free'd
==20899==    at 0x4A05A31: free (vg_replace_malloc.c:325)
==20899==    by 0x4004F6: main (t1.c:7)
==20899==
==20899==
==20899== HEAP SUMMARY:
==20899==     in use at exit: 0 bytes in 0 blocks
==20899==   total heap usage: 1 allocs, 2 frees, 100 bytes allocated
==20899==
==20899== All heap blocks were freed -- no leaks are possible
==20899==
==20899== For counts of detected and suppressed errors, rerun with: -v
==20899== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 4 from 4)
[sand@PS-CNTOS-64-S11 testbox]$

Una posible solución:

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

int main()
{
 char *x = malloc(100);
 free(x);
 x=NULL;
 free(x);
 return 0;
}

[sand@PS-CNTOS-64-S11 testbox]$ vim t1.c
[sand@PS-CNTOS-64-S11 testbox]$ cc -g t1.c -o t1
[sand@PS-CNTOS-64-S11 testbox]$ ./t1
[sand@PS-CNTOS-64-S11 testbox]$

[sand@PS-CNTOS-64-S11 testbox]$ valgrind --tool=memcheck --leak-check=full ./t1
==20958== Memcheck, a memory error detector
==20958== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==20958== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
==20958== Command: ./t1
==20958==
==20958==
==20958== HEAP SUMMARY:
==20958==     in use at exit: 0 bytes in 0 blocks
==20958==   total heap usage: 1 allocs, 1 frees, 100 bytes allocated
==20958==
==20958== All heap blocks were freed -- no leaks are possible
==20958==
==20958== For counts of detected and suppressed errors, rerun with: -v
==20958== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 4 from 4)
[sand@PS-CNTOS-64-S11 testbox]$

Consulte el blog sobre el uso de Valgrind Link

Sandipan Karmakar
fuente
Mi programa tarda unos 30 minutos en ejecutarse, en Valgrind puede tardar entre 18 y 20 horas en completarse.
Kemin Zhou
11

Con los compiladores modernos de C ++ puede utilizar desinfectantes para rastrear.

Ejemplo de muestra:

Mi programa:

$cat d_free.cxx 
#include<iostream>

using namespace std;

int main()

{
   int * i = new int();
   delete i;
   //i = NULL;
   delete i;
}

Compile con desinfectantes de direcciones:

# g++-7.1 d_free.cxx -Wall -Werror -fsanitize=address -g

Ejecutar:

# ./a.out 
=================================================================
==4836==ERROR: AddressSanitizer: attempting double-free on 0x602000000010 in thread T0:
    #0 0x7f35b2d7b3c8 in operator delete(void*, unsigned long) /media/sf_shared/gcc-7.1.0/libsanitizer/asan/asan_new_delete.cc:140
    #1 0x400b2c in main /media/sf_shared/jkr/cpp/d_free/d_free.cxx:11
    #2 0x7f35b2050c04 in __libc_start_main (/lib64/libc.so.6+0x21c04)
    #3 0x400a08  (/media/sf_shared/jkr/cpp/d_free/a.out+0x400a08)

0x602000000010 is located 0 bytes inside of 4-byte region [0x602000000010,0x602000000014)
freed by thread T0 here:
    #0 0x7f35b2d7b3c8 in operator delete(void*, unsigned long) /media/sf_shared/gcc-7.1.0/libsanitizer/asan/asan_new_delete.cc:140
    #1 0x400b1b in main /media/sf_shared/jkr/cpp/d_free/d_free.cxx:9
    #2 0x7f35b2050c04 in __libc_start_main (/lib64/libc.so.6+0x21c04)

previously allocated by thread T0 here:
    #0 0x7f35b2d7a040 in operator new(unsigned long) /media/sf_shared/gcc-7.1.0/libsanitizer/asan/asan_new_delete.cc:80
    #1 0x400ac9 in main /media/sf_shared/jkr/cpp/d_free/d_free.cxx:8
    #2 0x7f35b2050c04 in __libc_start_main (/lib64/libc.so.6+0x21c04)

SUMMARY: AddressSanitizer: double-free /media/sf_shared/gcc-7.1.0/libsanitizer/asan/asan_new_delete.cc:140 in operator delete(void*, unsigned long)
==4836==ABORTING

Para obtener más información sobre los desinfectantes, puede consultar la documentación de este o este o cualquier compilador moderno de C ++ (por ejemplo, gcc, clang, etc.).

Sitesh
fuente
5

¿Estás usando punteros inteligentes como Boost shared_ptr? Si es así, verifique si está usando directamente el puntero sin procesar en cualquier lugar llamandoget() . Descubrí que este es un problema bastante común.

Por ejemplo, imagine un escenario en el que se pasa un puntero sin procesar (tal vez como un controlador de devolución de llamada, por ejemplo) a su código. Puede decidir asignar esto a un puntero inteligente para hacer frente al recuento de referencias, etc. Gran error: su código no posee este puntero a menos que realice una copia profunda. Cuando su código termine con el puntero inteligente, lo destruirá e intentará destruir la memoria a la que apunta, ya que cree que nadie más lo necesita, pero el código de llamada intentará eliminarlo y obtendrá un doble problema libre.

Por supuesto, ese podría no ser su problema aquí. En su forma más simple, aquí hay un ejemplo que muestra cómo puede suceder. La primera eliminación está bien, pero el compilador detecta que ya se eliminó esa memoria y causa un problema. Es por eso que asignar 0 a un puntero inmediatamente después de la eliminación es una buena idea.

int main(int argc, char* argv[])
{
    char* ptr = new char[20];

    delete[] ptr;
    ptr = 0;  // Comment me out and watch me crash and burn.
    delete[] ptr;
}

Editar: cambiado deletea delete[], ya que ptr es una matriz de char.

Componente 10
fuente
No utilicé ningún comando de eliminación en mi programa. ¿Podría seguir siendo este el problema?
neuromante
1
@Phenom: ¿Por qué no usaste eliminaciones? ¿Es porque estás usando punteros inteligentes? ¿Presumiblemente estás usando new en tu código para crear objetos en el montón? Si la respuesta a ambos es sí, ¿está utilizando get / set en los punteros inteligentes para copiar los punteros sin procesar? Si es así, ¡no lo hagas! Estaría rompiendo el conteo de referencias. Alternativamente, podría asignar un puntero del código de la biblioteca al que está llamando a un puntero inteligente. Si no es "dueño" de la memoria a la que se apunta, no lo haga, ya que tanto la biblioteca como el puntero inteligente intentarán eliminarla.
Componente 10 de
-2

Sé que este es un hilo muy antiguo, pero es la principal búsqueda de Google para este error, y ninguna de las respuestas menciona una causa común del error.

Lo cual es cerrar un archivo que ya ha cerrado.

Si no está prestando atención y tiene dos funciones diferentes que cierran el mismo archivo, la segunda generará este error.

Jason
fuente
Estás equivocado, este error se produce debido a un doble libre, exactamente como indica el error. El hecho de que esté cerrando un archivo dos veces está provocando un doble libre, ya que claramente el método de cierre está tratando de liberar los mismos datos dos veces.
Geoffrey