Pergunta

Eu sou um membro de uma equipe que usar Delphi 2007 para uma aplicação maior e suspeitamos corrupção de pilha, porque às vezes há bugs estranhos que não têm outra explicação. Eu acredito que a opção Rangechecking para o compilador é apenas para matrizes. Eu quero uma ferramenta que dar uma exceção ou log quando há uma gravação em um endereço de memória que não é atribuída pela aplicação.

Saudações

Editar : O erro é do tipo:

Erro: Violação de acesso no endereço 00404E78 no módulo 'BoatLogisticsAMCAttracsServer.exe'. Leia de endereço FFFFFFDD

EDIT2 : Obrigado por todas as sugestões. Infelizmente eu acho que a solução é mais profundo do que isso. Nós usamos uma versão corrigida do Negrito para Delphi como nós próprios a fonte. Provavelmente existem alguns erros introduzidos no framwork Bold. Sim, temos um registro com callstacks que são manipulados por JCL e também mensagens de rastreamento. Assim, uma pilha de chamadas com exceção pode bloquear assim:

20091210 16:02:29 (2356) [EXCEPTION] Raised EBold: Failed to derive ServerSession.mayDropSession: Boolean
OCL expression: not active and not idle and timeout and (ApplicationKernel.allinstances->first.CurrentSession <> self)
Error: Access violation at address 00404E78 in module 'BoatLogisticsAMCAttracsServer.exe'. Read of address FFFFFFDD. At Location BoldSystem.TBoldMember.CalculateDerivedMemberWithExpression (BoldSystem.pas:4016)

Inner Exception Raised EBold: Failed to derive ServerSession.mayDropSession: Boolean
OCL expression: not active and not idle and timeout and (ApplicationKernel.allinstances->first.CurrentSession <> self)
Error: Access violation at address 00404E78 in module 'BoatLogisticsAMCAttracsServer.exe'. Read of address FFFFFFDD. At Location BoldSystem.TBoldMember.CalculateDerivedMemberWithExpression (BoldSystem.pas:4016)
Inner Exception Call Stack:
 [00] System.TObject.InheritsFrom (sys\system.pas:9237)

Call Stack:
 [00] BoldSystem.TBoldMember.CalculateDerivedMemberWithExpression (BoldSystem.pas:4016)
 [01] BoldSystem.TBoldMember.DeriveMember (BoldSystem.pas:3846)
 [02] BoldSystem.TBoldMemberDeriver.DoDeriveAndSubscribe (BoldSystem.pas:7491)
 [03] BoldDeriver.TBoldAbstractDeriver.DeriveAndSubscribe (BoldDeriver.pas:180)
 [04] BoldDeriver.TBoldAbstractDeriver.SetDeriverState (BoldDeriver.pas:262)
 [05] BoldDeriver.TBoldAbstractDeriver.Derive (BoldDeriver.pas:117)
 [06] BoldDeriver.TBoldAbstractDeriver.EnsureCurrent (BoldDeriver.pas:196)
 [07] BoldSystem.TBoldMember.EnsureContentsCurrent (BoldSystem.pas:4245)
 [08] BoldSystem.TBoldAttribute.EnsureNotNull (BoldSystem.pas:4813)
 [09] BoldAttributes.TBABoolean.GetAsBoolean (BoldAttributes.pas:3069)
 [10] BusinessClasses.TLogonSession._GetMayDropSession (code\BusinessClasses.pas:31854)
 [11] DMAttracsTimers.TAttracsTimerDataModule.RemoveDanglingLogonSessions (code\DMAttracsTimers.pas:237)
 [12] DMAttracsTimers.TAttracsTimerDataModule.UpdateServerTimeOnTimerTrig (code\DMAttracsTimers.pas:482)
 [13] DMAttracsTimers.TAttracsTimerDataModule.TimerKernelWork (code\DMAttracsTimers.pas:551)
 [14] DMAttracsTimers.TAttracsTimerDataModule.AttracsTimerTimer (code\DMAttracsTimers.pas:600)
 [15] ExtCtrls.TTimer.Timer (ExtCtrls.pas:2281)
 [16] Classes.StdWndProc (common\Classes.pas:11583)

A parte interior excepção é a pilha de chamadas no momento em que uma excepção é reraised.

EDIT3: A teoria agora é que a tabela de memória virtual (VMT) é de alguma forma quebrado. Quando isso acontecer não há nenhuma indicação disso. Somente quando um método é chamado uma exceção ( Sempre no endereço FFFFFFDD, -35 decimal), mas então é tarde demais. Você não sabe a causa real para o erro. Qualquer sugestão de como pegar um bug como este é muito apreciada !!! Temos tentado com SafeMM, mas o problema é que o consumo de memória é muito alto, mesmo quando a bandeira 3 GB é usado. Então agora eu tento dar uma recompensa para a comunidade SO:)

edit4: Uma dica é que, segundo o log há muitas vezes (ou mesmo sempre) outra exceção antes disso. Ele pode ser, por exemplo bloqueio optimista na base de dados. Temos tentado exceções levantam pela força, mas em ambiente de teste ele só funciona bem.

EDIT5: A história continua ... Eu fiz uma pesquisa sobre os registos para os últimos 30 dias agora. O resultado:

  • "Leia de endereço FFFFFFDB" 0
  • "Leia de endereço FFFFFFDC" 24
  • "Leia de endereço FFFFFFDD" 270
  • "Leia de FFFFFFDE endereço" 22
  • "Leia de FFFFFFDF endereço" 7
  • "Leia de endereço FFFFFFE0" 20
  • "Leia de endereço FFFFFFE1" 0

Assim, a teoria atual é que uma enumeração (há um monte em negrito) substituir um ponteiro. Eu tenho 5 hits com endereço diferente acima. Isso pode significar que o enum detém 5 valores, onde o segundo é a mais utilizada. Se há uma exceção a reversão deve ocorrer para o banco de dados e Boldobjects devem ser destruídos. Talvez há uma chance de que nem tudo é destruído e um enum ainda pode escrever para um local endereço. Se isso for verdade, talvez seja possível pesquisar o código por um RegExpr para um enum com 5 valores?

EDIT6: Para resumir, não existe nenhuma solução para o problema ainda. Eu percebo que eu posso enganá-lo um pouco com a pilha de chamadas. Sim, há um temporizador em que, mas há outras callstacks sem um temporizador. Desculpe por isso. Mas existem 2 factores comuns.

  • Uma excepção, no Read do endereço FFFFFFxx.
  • Topo da pilha de chamadas é System.TObject.InheritsFrom (SYS \ system.pas: 9237)

Esta me convencer de que VilleK melhor descrever o problema. Eu também estou convencido de que o problema está em algum lugar no quadro Bold. Mas o BIG pergunta é, como pode problemas como este pode ser resolvido? Não é o suficiente para ter um Assert como VilleK sugerem que o dano já ocorreu e a pilha de chamadas se foi naquele momento. Então, para descrever a minha visão do que pode causar o erro:

  1. Em algum lugar um ponteiro é atribuído um mau valor 1, mas pode ser também 0, 2, 3 etc.
  2. Um objeto é atribuído a esse ponteiro.
  3. Não é chamada de método nas baseclass objetos. Este método TObject.InheritsForm causa para ser chamado e uma exceção aparecem na FFFFFFDD endereço.

Esses 3 eventos podem estar juntos no código, mas eles também podem ser usados ??muito mais tarde. Eu acho que isso é verdade para a última chamada de método.

EDIT7: Nós trabalhamos de perto com o autor do Negrito Jan Norden e ele recentemente descobriu um bug no OCL-avaliador no quadro Bold. Quando isso foi corrigido esses tipos de exceções diminuiu muito, mas eles ainda ocasionalmente vir. Mas é um grande alívio que isso está quase resolvido.

Foi útil?

Solução

Eu não tenho uma solução, mas existem algumas pistas sobre que mensagem de erro particular.

System.TObject.InheritsFrom subtrai a constante vmtParent da Auto-ponteiro (a classe) para obter ponteiro para o endereço da classe pai.

Em Delphi 2007 vmtParent está definida:

vmtParent = -36;

Assim, o erro $ FFFFFFDD (-35) soa como o ponteiro de classe é de 1 neste caso.

Aqui está um caso de teste para reproduzi-lo:

procedure TForm1.FormCreate(Sender: TObject);
var
  I : integer;
  O : tobject;
begin
  I := 1;
  O := @I;
  O.InheritsFrom(TObject);
end;

Eu tentei-o em Delphi 2010 e obter 'Leia de endereço FFFFFFD1' porque o vmtParent é diferente entre versões do Delphi.

O problema é que isso acontece profundamente dentro do quadro Negrito assim você pode ter guarda problemas contra ele no código do aplicativo.

Você pode tentar isso em seus objetos que são usados ??na DMAttracsTimers-code (que eu suponho que é o código do aplicativo):

Assert(Integer(Obj.ClassType)<>1,'Corrupt vmt');

Outras dicas

Você escreve que você quer que haja uma exceção se

há uma gravação em um endereço de memória que não é atribuída pela aplicação

mas isso acontece de qualquer maneira, tanto o href="http://en.wikipedia.org/wiki/Memory_management_unit" rel="nofollow noreferrer"> OS se certificar disso.

Se você quer dizer que deseja verificar para gravações de memória inválidos no intervalo de endereços alocados da sua aplicação, então não há tanta coisa que você pode fazer. Você deve usar FastMM4 , e usá-lo com suas configurações mais detalhado e paranóicos no modo de depuração do seu aplicativo . Isso vai pegar um monte de gravações inválidos, acessos a memória já liberada e tal, mas não pode pegar tudo. Considere um apontador pendente que aponta para outro local de memória gravável (como no meio de uma grande cadeia ou matriz de valores float) - escrita a ele será bem-sucedida, e ele irá lixo outros dados, mas não há nenhuma maneira para que o gerenciador de memória para capturar tais acesso.

Parece que você tem corrupção de memória de dados objeto de instância.

A VMT em si não é ficar corrompido, FWIW: o VMT é (normalmente) armazenado no executável e as páginas que mapeiam para ele são somente leitura. Pelo contrário, como diz VilleK, parece que o primeiro campo dos dados de instância em seu caso foi substituído por um inteiro de 32 bits com o valor 1. Este é bastante fácil de verificar: verifique os dados de instância do objeto cuja chamada de método falhou, e verificar se a primeira DWORD é 00000001.

Se ele é realmente o ponteiro VMT nos dados de instância que está sendo corrompido, aqui está como eu gostaria de encontrar o código que corrompe-lo:

  1. Certifique-se de que há uma forma automatizada para reproduzir o problema que não requer entrada do usuário. O problema pode ser apenas reprodutível em uma única máquina, sem reinicializações entre reproduções devido à forma como o Windows pode optar por colocar para fora de memória.

  2. reproduzir o problema e anote o endereço dos dados instância cuja memória está corrompido.

  3. Execute novamente e verifique a segunda reprodução:. Certifique-se de que o endereço dos dados de instância que foi danificado na segunda corrida é o mesmo que o endereço da primeira corrida

  4. Agora, passo para um terceiro prazo, colocar um ponto de interrupção de dados de 4 bytes na seção de memória indicado pelas duas anteriores corridas. O ponto é quebrar em cada modificação para esta memória. Pelo menos uma ruptura deve ser a chamada TObject.InitInstance que preenche o ponteiro VMT; pode haver outros relacionados à construção instância, tal como no alocador de memória; e, no pior caso, os dados instância competente pode ter sido reciclado memória de instâncias anteriores. Para reduzir a quantidade de pisar necessário, tornar os dados de ponto de interrupção registrar a pilha de chamadas, mas na verdade não quebrar. Ao verificar as pilhas de chamada após a chamada virtual falhar, você deve ser capaz de encontrar o mau escrita.

mghie é certo, claro. (FastMM4 chama a fulldebugmode bandeira ou algo parecido).

Note que que as obras geralmente com barreiras apenas antes e depois de uma alocação de pilha que são verificados regularmente (em cada acesso heapmgr?).

Isto tem duas consequências:

  • o lugar onde FastMM detecta o erro pode desviar-se do local onde acontece
  • a gravação aleatória total (não excesso de alocação existente) não pode ser detectado.

Então, aqui estão algumas outras coisas para pensar:

  • ativar a verificação de tempo de execução
  • rever advertências tudo do seu compilador.
  • Tente compilar com uma versão delphi diferente ou FPC. Outros compiladores / RTLS / heapmanagers ter diferentes layouts, e que poderia levar ao ser pego erro mais fácil.

Se que todos os rendimentos nada, tente simplificar a aplicação até que ele vai embora. Então investigar as mais recentes peças comentadas / ifdefed.

A primeira coisa que eu faria é add MadExcept à sua candidatura e obter um rastreamento de pilha que imprime a árvore exata chamando, que lhe dará uma idéia do que está acontecendo aqui. Em vez de uma exceção aleatória e um endereço de memória binária / hex, você precisa ver uma árvore chamada, com os valores de todos os parâmetros e variáveis ??locais da pilha.

Se eu corrupção de memória suspeito em uma estrutura que é a chave para o meu pedido, eu, muitas vezes, escrever código extra para fazer o acompanhamento deste bug possível.

Por exemplo, em estruturas de memória (classe ou gravar tipos) pode ser providenciado para um Magic1: Palavra no início e um magic2: Palavra no final de cada registro na memória. Uma função de verificação de integridade pode verificar a integridade dessas estruturas por olhar para ver para cada registro Magic1 e magic2 não foram alteradas desde que eles foram criados no construtor. O Destructor mudaria Magic1 e magic2 a outros valores como a $ FFFF.

Também gostaria de considerar a adição de traço-logging ao meu pedido. registro de rastreamento em aplicações Delphi muitas vezes começa com me declarar uma forma TraceForm, com um TMemo lá, eo TraceForm.Trace (msg: String) função começa como "Memo1.Lines.Add (msg)". Como meu aplicativo amadurece, as instalações registro de rastreamento são a maneira que eu assistir a execução de aplicações para os padrões globais em seu comportamento, e mau comportamento. Então, quando um "aleatório" crash ou memória corrupção com "nenhuma explicação" acontece, eu tenho um registo de rastreio para voltar através de e ver o que tem levado a este caso particular.

Às vezes não é a corrupção de memória, mas erros básicos simples (eu esqueci de verificar se X é atribuído, então eu vou dereference que:. X.DoSomething (...) que assume X é atribuído, mas não é

Eu observei que um temporizador está no rastreamento de pilha.
Eu vi um monte de erros estranhos, onde a causa foi o evento timer é acionado após a forma i free'ed.
A razão é que um evento do temporizador Cound ser colocado no that mensagem e Noge são processadas brfor a destruição de outros componentes.
Uma maneira de contornar esse problema é desabilitar o temporizador como a primeira entrada na destruir do formulário. Após desativar os Application.ProcessMessages chamada tempo, de modo que qualquer eventos do temporizador é processado antes de destruir os componentes.
Outra maneira é verificar se o formulário está destruindo na TimerEvent. (CsDestroying em componentstate).

Você pode postar o código fonte deste procedimento?

BoldSystem.TBoldMember.CalculateDerivedMemberWithExpression (BoldSystem.pas: 4016)

Assim, podemos ver o que está acontecendo na linha 4016.

E também o ponto de vista CPU desta função?
(Apenas definir um ponto de interrupção na linha 4016 deste procedimento e executar. E copiar + colar o conteúdo vista CPU se você acertar o ponto de interrupção).
Assim, podemos ver que a instrução CPU é no endereço 00404E78.

Pode haver um problema com o código re-entrantes?

Tente colocar algum código de guarda em torno do código TTimer manipulador de eventos:

procedure TAttracsTimerDataModule.AttracsTimerTimer(ASender: TObject);
begin
  if FInTimer then
  begin
    // Let us know there is a problem or log it to a file, or something. 
    // Even throw an exception
    OutputDebugString('Timer called re-entrantly!'); 
    Exit; //======> 
  end;

  FInTimer := True;
  try

    // method contents

  finally
    FInTimer := False;
  end;
end;

N @

Eu acho que há outra possibilidade: o cronômetro é disparado para verificar se há "oscilação sessões de logon". Em seguida, uma chamada é feita em um objeto TLogonSession para verificar se ele pode ser descartado (_GetMayDropSession), certo? Mas e se o objeto já está destruído? Talvez devido à rosca questões de segurança ou apenas uma chamada .Free e não uma chamada FreeAndNil (para uma variável é ainda <> nil) etc etc. Nesse meio tempo, outros objetos são criados para a memória é reutilizada. Se você tentar acesso a variável algum tempo depois, você pode / vai ter erros aleatórios ...

Um exemplo:

procedure TForm11.Button1Click(Sender: TObject);
var
  c: TComponent;
  i: Integer;
  p: pointer;
begin
  //create
  c := TComponent.Create(nil);
  //get size and memory
  i := c.InstanceSize;
  p := Pointer(c);
  //destroy component
  c.Free;
  //this call will succeed, object is gone, but memory still "valid"
  c.InheritsFrom(TObject);
  //overwrite memory
  FillChar(p, i, 1);
  //CRASH!
  c.InheritsFrom(TObject);
end;

Violação de acesso no endereço 004619D9 no módulo 'Project10.exe'. Leia de endereço 01010101.

Não é o problema que "_GetMayDropSession" faz referência a uma variável de sessão libertado?

Eu já vi esse tipo de erros antes, no TMS onde os objetos foram libertados e referenciado em um onchange etc (somente em algumas situações, deu erros, muito difíceis / impossíveis de reproduzir, é corrigido agora pela TMS :-)). Também com sessões RemObjects eu tenho algo semelhante (devido ao mau bug de programação por mim).

Gostaria de tentar adicionar uma variável dummy para a classe sessão e cheque de seu valor:

  • iMagicNumber variável pública: integer;
  • construtor criar: iMagicNumber: = 1234567;
  • destruidor destruir: iMagicNumber: = 1;
  • "outros procedimentos": assert (iMagicNumber = 1234567)
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top