corrupção Heap em Win32; como localizar?

votos
54

Eu estou trabalhando em um de vários segmentos aplicativo C ++ que está corrompendo o heap. As ferramentas usuais para localizar essa corrupção parece ser inaplicável. Constrói (18 meses de idade) da exposição código fonte o mesmo comportamento que a versão mais recente de idade, e isso tem sido em torno de um longo tempo e só não foi notado; Em contrapartida, deltas de origem não pode ser usado para identificar quando o bug foi introduzido - há um monte de mudanças de código no repositório.

A linha de comandos para bater behaviuor é gerar o rendimento neste sistema - a transferência de tomada de dados que está em munged uma representação interna. Eu tenho um conjunto de dados de teste que fará com que periodicamente o aplicativo exceção (vários locais, várias causas - incluindo pilha de alocação falha, assim: corrupção de pilha).

O comportamento parece estar relacionada com poder de CPU ou largura de banda de memória; a mais de cada máquina tem, mais fácil é a falhar. Desactivar um núcleo hiper-threading ou um núcleo de dois núcleos reduz a taxa de (mas não elimina) corrupção. Isto sugere uma questão relacionada timing.

Agora, aqui está o busílis:
Quando ele é executado em um ambiente de depuração leve (digamos Visual Studio 98 / AKA MSVC6) a corrupção de pilha é razoavelmente fácil de reproduzir - dez ou quinze minutos passam antes que algo falha terrivelmente e exceções, como um alloc;quando executado em um ambiente de depuração sofisticado (Rational Purify, VS2008/MSVC9ou até mesmo Microsoft Application Verifier), o sistema torna-se a memória de velocidade amarrado e não falha (ligado memória-: CPU não está recebendo acima 50%, luz do disco não estiver ligado, o programa vai tão rápido que pode, caixa de consumir 1.3Gde 2G de memória RAM) . Então, eu tenho uma escolha entre ser capaz de reproduzir o problema (mas não identificar a causa) ou ser capaz de idenify a causa ou um problema que não pode reproduzir.

Meus atuais melhores suposições a respeito de onde a próxima é:

  1. Obter uma caixa grunty insanamente (para substituir a caixa de dev atual: 2GB de RAM em uma E6550 Core2 Duo); isto irá tornar possível repro o acidente causando mal-comportamento quando executado em um ambiente de depuração poderosa; ou
  2. Reescrever operadores newe deletede usar VirtualAlloce VirtualProtectpara marcar a memória como somente leitura, assim que ele é feito com. Executar como MSVC6e ter o OS pegar o bad-guy que está escrevendo a memória liberada. Sim, este é um sinal de desespero: Quem regravações inferno newe delete?! Eu me pergunto se isso vai torná-lo tão lento como no Purify et al.

E, não: Envio com Purify instrumentação construída em não é uma opção.

Um colega apenas orientado passado e perguntou: Stack Overflow? Será que estamos recebendo pilha transborda agora?!?

E agora, a pergunta: Como faço para localizar o corruptor pilha?


Update: equilíbrio new[]e delete[]parece ter ficado um longo caminho para resolver o problema. Em vez de 15mins, o aplicativo vai agora cerca de duas horas antes de cair. Não existe ainda. Quaisquer outras sugestões? A corrupção de pilha persiste.

Update: uma compilação de lançamento em Visual Studio 2008 parece muito melhor; suspeita atual repousa sobre a STLimplementação que acompanha VS98.


  1. Reproduzir o problema. Dr Watsonirá produzir uma descarga que pode ser útil para uma análise mais aprofundada.

Eu vou tomar nota disso, mas eu estou preocupado que o Dr. Watson só será tropeçar após o fato, não quando a pilha está sendo pisoteada.

Outra tentativa pode estar usando WinDebugcomo uma ferramenta de depuração que é bastante poderoso estar em também ao mesmo tempo leve.

Tem que ir no momento, mais uma vez: não ajuda muito até que algo dá errado. Eu quero pegar o vandalismo no ato.

Talvez estas ferramentas permitirá que você, pelo menos, para reduzir o problema a determinado componente.

Eu não segure muita esperança, mas os tempos desesperados exigem ...

E você tem certeza de que todos os componentes do projeto têm configurações da biblioteca de tempo de execução corretos ( C/C++ tab, categoria de geração de código no VS configurações 6.0 projeto)?

Não, eu não sou, e eu vou passar um par de horas de amanhã atravessando o espaço de trabalho (58 projetos no mesmo) e verificar que eles estão todos compilação e vinculação com as bandeiras apropriadas.


Update: Este levou 30 segundos. Selecione todos os projetos na Settingscaixa de diálogo, desmarque até encontrar o projeto (s) que não têm as configurações corretas (todos eles tinham as configurações corretas).

Publicado 04/08/2008 em 08:30
fonte usuário
Em outras línguas...                            


15 respostas

votos
26

Minha primeira escolha seria uma ferramenta heap dedicada, como Pageheap.exe .

Reescrevendo novo e excluir pode ser útil, mas isso não pegar as alocações cometidos por código de nível mais baixo. Se é isso que você quer, melhor para Detour os low-level alloc APIs que usam o Microsoft Detours.

Também checagens, tais como: verificar a sua bibliotecas de tempo de execução jogo (release vs. depuração, multi-threaded vs. single-threaded, dll vs. lib estática), procure exclusões ruins (por exemplo, excluir, onde excluir [] deveria ter sido utilizado), certifique-se que você não está misturando e combinando suas alocações.

Além disso, tente seletivamente desligar tópicos e ver quando / se o problema desaparece.

O que a pilha de chamadas etc parecer no momento da primeira exceção?

Respondeu 04/08/2008 em 08:51
fonte usuário

votos
11

Tenho mesmos problemas no meu trabalho (também usamos VC6às vezes). E não há solução fácil para isso. Tenho apenas algumas dicas:

  • Tente com queda automática despeja na máquina de produção (ver Dumper de processo ). A minha experiência diz Dr. Watson é não perfeito para despejar.
  • Remover tudo catch (...) do seu código. Eles muitas vezes escondem memória exceções graves.
  • Verifique avançadas do Windows Debugging - há muitas ótimas dicas para problemas como a sua. Eu recomendo isso com todo o meu coração.
  • Se você usar STLtente STLPorte compilações verificadas. Iterador inválido são o inferno.

Boa sorte. Problemas como a sua nos levar meses para resolver. Esteja pronto para este ...

Respondeu 06/08/2008 em 13:41
fonte usuário

votos
8

Execute o aplicativo original com ADplus -crash -pn appnename.exe Quando o problema de memória pop-up que você vai ter um grande agradável despejo.

Você pode analisar o despejo de descobrir o local de memória foi corrompido. Se você tiver sorte a memória de substituição é uma string exclusiva que você pode descobrir de onde veio. Se você não tiver sorte, você vai precisar cavar win32pilha e descobrir o que era as características de memória orignal. (pilha -x pode ajudar)

Depois de saber o que estava confusa, você pode restringir o AppVerifier uso com configurações especiais heap. ou seja, você pode especificar o que DLLvocê monitore, ou o tamanho de atribuição de monitorar.

Esperemos que isto irá speedup a monitorização suficiente para pegar o culpado.

Na minha experiência, eu nunca precisei modo pilha verificador completo, mas eu passei muito tempo analisando o despejo de memória (s) e as fontes de navegação.

PS: Você pode usar DebugDiag para analisar os depósitos. Ele pode apontar a DLLpossuir a pilha corrompido, e dar-lhe outros detalhes usefull.

Respondeu 16/09/2008 em 08:33
fonte usuário

votos
7

Nós tivemos muito boa sorte escrevendo nossas próprias funções malloc e free. Na produção, eles simplesmente chamar o malloc padrão e livre, mas na depuração, eles podem fazer o que quiser. Nós também temos uma classe base simples que não faz nada, mas substituir o novo e excluir operadores para usar estas funções, então qualquer classe que você escrever pode simplesmente herdar essa classe. Se você tem uma tonelada de código, ele pode ser um grande trabalho para substituir chamadas para malloc e livre para o novo malloc e livre (não se esqueça realloc!), Mas a longo prazo é muito útil.

No livro de Steve Maguire Escrever código sólido (altamente recomendável), há exemplos de material de depuração que você pode fazer nestas rotinas, como:

  • Mantenha o controle de alocações para encontrar vazamentos
  • Alocar mais memória do que o necessário e colocar marcadores no início e no final da memória - durante a rotina livre, você pode garantir que estes marcadores são ainda lá
  • memset a memória com um marcador na alocação (para encontrar o uso de memória não inicializada) e à livre (para encontrar o uso da memória free'd)

Outra boa idéia é não usar coisas como strcpy, strcatou sprintf- sempre uso strncpy, strncate snprintf. Nós escrevemos nossas próprias versões destes, bem como, para se certificar de que não amortizar o final de um buffer, e estas têm pego muitos problemas também.

Respondeu 22/08/2008 em 18:11
fonte usuário

votos
4

Você deve atacar este problema com tanto tempo de execução e análise estática.

Para a análise estática considerar compilar com PREfast ( cl.exe /analyze). Ele detecta incompatíveis deletee delete[], estouros de buffer e uma série de outros problemas. Esteja preparado, porém, para percorrer muitos kilobytes de aviso L6, especialmente se o seu projeto ainda tem L4não fixo.

PREfast está disponível com Visual Studio Team System e, aparentemente , como parte do Windows SDK.

Respondeu 12/10/2008 em 22:55
fonte usuário

votos
3

É este, em condições de pouca memória? Se assim for, pode ser que nova está retornando NULLao invés de jogar std :: bad_alloc. Mais velhos VC++compiladores não adequadamente implementar isso. Há um artigo sobre legados falhas de alocação de memória quebrando STLaplicativos construídos com VC6.

Respondeu 02/09/2008 em 07:03
fonte usuário

votos
3

A aparente aleatoriedade da corrupção de memória soa muito como um problema de sincronização rosca - um erro é reproduzido dependendo da velocidade da máquina. Se os objetos (chuncks de memória) são compartilhados entre threads e sincronização (seção crítica, mutex, semáforo, outros) primitivas não estão em per-classe (por objeto, por classe) base, então é possível chegar a uma situação onde classe (bloco de memória) é excluído / libertado durante o uso, ou utilizado após excluído / libertados.

Como um teste para isso, você pode adicionar primitivas de sincronização para cada classe e método. Isso fará com que seu código mais lento porque muitos objetos terá que esperar para o outro, mas se isso elimina a corrupção de pilha, seu problema pilha-corrupção vai se tornar uma otimização de código um.

Respondeu 25/08/2008 em 20:55
fonte usuário

votos
1

Se você optar por reescrever nova / apagar, eu fiz isso e ter o código fonte simples em:

http://gandolf.homelinux.org/~smhanov/blog/?id=10

Isso chama vazamentos de memória e também insere dados guarda antes e depois do bloco de memória para capturar corrupção de pilha. Você pode apenas integrar com ele, colocando #include "Debug.h" no topo de cada arquivo CPP, e definindo depurar e DEBUG_MEM.

Respondeu 17/09/2008 em 14:40
fonte usuário

votos
1

Então, a partir da informação limitada que tem, isso pode ser uma combinação de um ou mais coisas:

  • uso Bad montão, ou seja, liberta casal, lido após livre, escrever depois livre, definindo o sinalizador HEAP_NO_SERIALIZE com alocações e libera a partir de múltiplas threads na mesma pilha
  • Fora da memória
  • Bad código (isto é, sobrecargas de buffer, underflows tampão, etc.)
  • questões "Tempo de espera"

Se é em tudo o primeiro dois, mas não o último, você deve ter apanhado até agora com qualquer Pageheap.exe.

O que provavelmente significa que é devido à forma como o código está acessando a memória compartilhada. Infelizmente, o acompanhamento que para baixo vai ser bastante doloroso. Acesso Unsynchronized à memória compartilhada muitas vezes se manifesta como estranhas questões "de tempo". Coisas como não usar a semântica aquisição / liberação para sincronizar o acesso à memória compartilhada com uma bandeira, e não o uso de bloqueios de forma adequada, etc.

No mínimo, seria útil para ser capaz de controlar as alocações de alguma forma, como foi sugerido anteriormente. Pelo menos, então você pode ver o que realmente aconteceu até o corrupção de pilha e tentar diagnosticar a partir daí.

Além disso, se você pode facilmente redirecionar verbas para vários montes, você pode querer tentar isso para ver se que ou corrige o problema ou resulta em comportamento de buggy mais reprodutíveis.

Quando estavam testando com VS2008, você correu com HeapVerifier com conservar a memória definida como Sim? Isso pode reduzir o impacto do alocador de heap desempenho. (Além disso, você tem que correr com ele Debug-> Comece com o Application Verifier, mas você já deve saber disso.)

Você também pode tentar a depuração com WinDBG e vários usos do comando montão!.

MSN

Respondeu 22/08/2008 em 17:51
fonte usuário

votos
0

Um par de sugestões. Você menciona as advertências copiosas no W4 - eu sugeriria tomar o tempo para corrigir o seu código para compilar limpa no nível de aviso 4 - o que irá percorrer um longo caminho para prevenir sutil difícil encontrar bugs.

Em segundo lugar - para o / analisar interruptor - é, de fato, gerar avisos copiosas. Para usar essa opção no meu próprio projeto, o que eu fiz foi criar um novo arquivo de cabeçalho que utilizado # pragma aviso para desligar todos os avisos adicionais gerados pela / analisar. Em seguida, mais abaixo no arquivo, eu ligar apenas os avisos que me interessa. Em seguida, use o parâmetro / compilador FI para forçar esse arquivo de cabeçalho para ser incluído pela primeira vez em todas as suas unidades de compilação. Isso deve permitir que você use o / analisar interruptor enquanto controlando a saída

Respondeu 03/10/2009 em 17:48
fonte usuário

votos
0

Você acha que esta é uma condição de corrida? São vários segmentos compartilhando uma pilha? você pode dar a cada thread uma pilha privada com HeapCreate, então eles podem correr rápido com HEAP_NO_SERIALIZE. Caso contrário, a pilha deve ser thread-safe, se você estiver usando a versão multi-threaded das bibliotecas do sistema.

Respondeu 30/07/2009 em 14:48
fonte usuário

votos
0

O pouco tempo que eu tinha para resolver um problema semelhante. Se o problema persistir eu sugiro que você faça o seguinte: Monitorar todas as chamadas para novo / apagar e malloc / calloc / realloc / livre. Eu faço DLL única exportar uma função para registo de todas as chamadas. Esta função recebe um parâmetro para identificar o seu código fonte, ponteiro para a área alocada e tipo de salvar essas informações em uma tabela chamada. Todos alocado / par libertado é eliminado. No final ou depois que você precisa fazer uma chamada para uma outra função para criar relatório para dados de esquerda. Com isso, você pode identificar as chamadas erradas (nova / gratuitos ou malloc / delete) ou ausentes. Se tiver qualquer caso de tampão substituído em seu código a informação guardada pode estar errado, mas cada teste pode detectar / descobrir / incluem uma solução de falha identificada. Muitas corridas para ajudar a identificar os erros. Boa sorte.

Respondeu 19/12/2008 em 12:52
fonte usuário

votos
0

sugestão de malloc / free costume de Graeme é uma boa idéia. Veja se você pode caracterizar um padrão sobre a corrupção para dar-lhe um identificador para alavancar.

Por exemplo, se é sempre em um bloco do mesmo tamanho (digamos 64 bytes), em seguida, mudar o seu malloc / free par de sempre alocar 64 blocos de bytes em sua própria página. Quando você liberar um pedaço de 64 bytes, em seguida, definir os bits de proteção de memória nessa página para impedir que lê e wites (usando VirtualQuery). Em seguida, qualquer tentativa de acessar essa memória vai gerar uma exceção em vez de corromper o heap.

Isto assume que o número de pendentes pedaços de 64 bytes é apenas moderada ou você tem uma grande quantidade de memória para queimar na caixa!

Respondeu 02/09/2008 em 05:23
fonte usuário

votos
0

Minha primeira ação seria a seguinte:

  1. Construir os binários na versão "Release", mas a criação de depuração arquivo info (você vai encontrar essa possibilidade em configurações do projeto).
  2. Use Dr. Watson como um depurador defualt (drwtsn32-i) em uma máquina na qual deseja reproduzir o problema.
  3. Repdroduce o problema. Dr. Watson irá produzir uma descarga que pode ser útil para uma análise mais aprofundada.

Outra tentativa pode estar usando WinDebug como uma ferramenta de depuração que é bastante poderoso estar em também ao mesmo tempo leve.

Talvez estas ferramentas permitirá que você, pelo menos, para reduzir o problema a determinado componente.

E você tem certeza de que todos os componentes do projeto têm configurações da biblioteca de tempo de execução corretos (separador C / C ++, categoria de geração de código no VS configurações 6.0 projeto)?

Respondeu 04/08/2008 em 09:26
fonte usuário

votos
0

Você tentou velho constrói, mas há uma razão que você não pode manter-se ir mais para trás na história repositório e ver exatamente quando o bug foi introduzido?

Caso contrário, gostaria de sugerir a adição de registro simples de algum tipo para ajudar a rastrear o problema, embora eu estou em uma perda sobre o que especificamente você pode querer fazer logon.

Se você pode descobrir o que exatamente pode causar este problema, através do google e documentação das excepções que você está recebendo, talvez isso vai dar mais uma visão sobre o que procurar no código.

Respondeu 04/08/2008 em 08:48
fonte usuário

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more