Como rastrear um erro de “duplo livre ou corrompido”

95

Quando executo meu programa (C ++), ele trava com este erro.

* glibc detectado * ./load: double free ou corrompido (! prev): 0x0000000000c6ed50 ***

Como posso rastrear o erro?

Tentei usar std::coutinstruções print ( ), sem sucesso. Poderia gdbtornar isso mais fácil?

neuromancer
fonte
5
Eu me pergunto por que todo mundo sugere NULLponteiros (que mascaram erros que de outra forma seriam detectados, como essa pergunta mostra), mas ninguém sugere simplesmente não fazer gerenciamento manual de memória, o que é muito bem possível em C ++. Eu não escrevo há deleteanos. (E, sim, meu código é crítico para o desempenho. Caso contrário, não teria sido escrito em C ++.)
sbi
2
@sbi: Corrupção de heap e coisas semelhantes raramente são detectadas, pelo menos não onde ocorrem. NULLponteiros podem fazer seu programa travar mais cedo.
Hasturkun
@Hasturkun: Discordo totalmente. Um grande incentivo aos NULLponteiros é evitar que um segundo delete ptr;exploda - o que está mascarando um erro, porque aquele segundo deletenunca deveria ter acontecido. (Também é usado para verificar se um ponteiro ainda está apontando para um objeto válido. Mas isso só levanta a questão de por que você tem um ponteiro no escopo que não tem um objeto para apontar.)
sbi

Respostas:

64

Se você estiver usando glibc, você pode definir a MALLOC_CHECK_variável de ambiente para 2, isso fará com que a glibc use uma versão tolerante a erros domalloc , o que fará com que seu programa aborte no ponto onde o double free é feito.

Você pode definir isso a partir do gdb usando o set environment MALLOC_CHECK_ 2comando antes de executar o programa; o programa deve abortar, com a free()chamada visível no backtrace.

veja a páginamalloc() do manual para mais informações

Hasturkun
fonte
2
A configuração, MALLOC_CHECK_2na verdade, corrigiu meu problema gratuito duplo (embora não esteja corrigindo se estiver no modo de depuração apenas)
puk
4
@puk Tenho o mesmo problema, definir MALLOC_CHECK_ para 2 evita meu problema de liberação dupla. Que outras opções existem para injetar menos código para reproduzir o problema e fornecer um backtrace?
Wei Zhong
Tenha também onde configurar MALLOC_CHECK_ evita o problema. Outros comentaristas / qualquer pessoa ... você descobriu uma maneira diferente de exibir o problema?
pellucidcoder
"Quando MALLOC_CHECK_ é definido com um valor diferente de zero, uma implementação especial (menos eficiente) é usada, a qual é projetada para ser tolerante contra erros simples, como chamadas duplas de grátis com o mesmo argumento, ou saturações de um único byte (desligado -por-um bugs). " gnu.org/software/libc/manual/html_node/… Portanto, parece que MALLOC_CHECK_ é usado apenas para evitar erros de memória simples, não detectá-los.
pellucidcoder
Na verdade .... support.microfocus.com/kb/doc.php?id=3113982 parece que definir MALLOC_CHECK_ para 3 é o mais útil e pode ser usado para detectar erros.
pellucidcoder
33

Existem pelo menos duas situações possíveis:

  1. você está excluindo a mesma entidade duas vezes
  2. você está excluindo algo que não foi alocado

Para o primeiro, eu sugiro enfaticamente anular todos os ponteiros excluídos.

Você tem três opções:

  1. sobrecarregar novo e excluir e rastrear as alocações
  2. sim, use gdb - então você obterá um backtrace de sua falha, e isso provavelmente será muito útil
  3. como sugerido - use Valgrind - não é fácil de entrar, mas você vai economizar milhares de vezes no futuro ...
Kornel Kisielewicz
fonte
2. causaria corrupção, mas não acho que essa mensagem geralmente apareceria, uma vez que a verificação de integridade é feita apenas no heap. No entanto, acho que 3. o estouro do buffer de heap é possível.
Matthew Flaschen
Um bom. Verdade, eu perdi o ponteiro para tornar o ponteiro NULL e me deparei com esse erro. Lições aprendidas!
hrushi
26

Você pode usar o gdb, mas primeiro tentaria o Valgrind . Consulte o guia de início rápido .

Resumidamente, Valgrind instrumenta seu programa para que possa detectar vários tipos de erros no uso de memória alocada dinamicamente, como liberações duplas e gravações após o final dos blocos alocados de memória (que podem corromper o heap). Ele detecta e relata os erros assim que ocorrem , apontando assim diretamente para a causa do problema.

Matthew Flaschen
fonte
1
@SMR, neste caso, as partes essenciais da resposta é a página inteira, grande e vinculada. Portanto, incluir apenas o link na resposta é perfeitamente adequado. Algumas palavras sobre por que o autor prefere Valgrind ao gdb e como ele enfrentaria o problema específico é IMHO o que está realmente faltando na resposta.
ndemou
20

Três regras básicas:

  1. Definir o ponteiro para NULLdepois de livre
  2. Verifique NULLantes de liberar.
  3. Inicialize o ponteiro para NULLno início.

A combinação desses três funciona muito bem.

Jack
fonte
1
Não sou especialista em C, mas geralmente consigo manter minha cabeça acima da água. Por que nº 1? É apenas para que o seu programa trave quando você tenta acessar um ponteiro livre, e não apenas um erro silencioso?
Daniel Harms
1
@Precision: Sim, esse é o ponto. É uma boa prática: ter um ponteiro para a memória excluída é um risco.
Em
10
Estritamente falando, acho que o nº 2 é desnecessário, pois a maioria dos compiladores permitirá que você tente excluir um ponteiro nulo sem que isso cause problemas. Tenho certeza que alguém vai me corrigir se eu estiver errado. :)
Componente 10
11
@ Component10 Acho que a liberação de NULL é exigida pelo padrão C para não fazer nada.
Demi
2
@Demetri: Sim, você está certo "se o valor do operando de exclusão for o ponteiro nulo, a operação não tem efeito." (ISO / IEC 14882: 2003 (E) 5.3.5.2)
Componente 10 de
16

Você pode usar valgrindpara depurá-lo.

#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]$

Uma solução possível:

#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]$

Confira o blog sobre como usar o Valgrind Link

Sandipan Karmakar
fonte
Meu programa leva cerca de 30 minutos para ser executado, no Valgrind pode levar de 18 a 20 horas para terminar.
Kemin Zhou
13

Com compiladores C ++ modernos, você pode usar desinfetantes para rastrear.

Exemplo de amostra:

Meu programa:

$cat d_free.cxx 
#include<iostream>

using namespace std;

int main()

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

Compile com desinfetantes de endereço:

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

Execute:

# ./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 aprender mais sobre desinfetantes, você pode verificar esta ou esta ou qualquer outra documentação de compiladores c ++ modernos (por exemplo, gcc, clang etc.).

Sitesh
fonte
5

Você está usando ponteiros inteligentes, como Boost shared_ptr? Em caso afirmativo, verifique se você está usando diretamente o ponteiro bruto em qualquer lugar chamando get(). Descobri que este é um problema bastante comum.

Por exemplo, imagine um cenário onde um ponteiro bruto é passado (talvez como um manipulador de retorno de chamada, digamos) para seu código. Você pode decidir atribuí-lo a um ponteiro inteligente para lidar com a contagem de referência, etc. Grande erro: seu código não possui este ponteiro a menos que você faça uma cópia profunda. Quando seu código estiver pronto com o ponteiro inteligente, ele irá destruí-lo e tentar destruir a memória para a qual aponta, uma vez que pensa que ninguém mais precisa dele, mas o código de chamada tentará excluí-lo e você obterá um duplo problema grátis.

Claro, esse pode não ser o seu problema aqui. Na sua forma mais simples, aqui está um exemplo que mostra como isso pode acontecer. A primeira exclusão está bem, mas o compilador percebe que essa memória já foi excluída e causa um problema. É por isso que atribuir 0 a um ponteiro imediatamente após a exclusão é uma boa ideia.

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;
}

Edit: alterado deletepara delete[], pois ptr é um array de char.

Componente 10
fonte
Não usei nenhum comando delete no meu programa. Esse ainda poderia ser o problema?
neuromancer
1
@Phenom: Por que você não usou exclusões? É porque você está usando ponteiros inteligentes? Presumivelmente, você está usando new em seu código para criar objetos no heap? Se a resposta a ambas for sim, então você está usando get / set nos ponteiros inteligentes para copiar ponteiros brutos? Se sim, não faça isso! Você estaria quebrando a contagem de referência. Como alternativa, você pode atribuir um ponteiro do código da biblioteca que está chamando a um ponteiro inteligente. Se você não 'possuir' a memória apontada, não o faça, pois tanto a biblioteca quanto o ponteiro inteligente tentarão excluí-la.
Componente 10
-2

Eu sei que este é um tópico muito antigo, mas é a principal pesquisa do Google para esse erro, e nenhuma das respostas menciona uma causa comum do erro.

Que está fechando um arquivo que você já fechou.

Se você não estiver prestando atenção e tiver duas funções diferentes fechando o mesmo arquivo, a segunda irá gerar este erro.

Jason
fonte
Você está incorreto, este erro é lançado devido a um double free, exatamente como o erro indica. O fato de você estar fechando um arquivo duas vezes está causando uma liberação dupla, pois o método de fechamento está tentando liberar os mesmos dados duas vezes.
Geoffrey