Domanda

Sono un membro in una squadra che utilizzano Delphi 2007 per un'applicazione più grande e abbiamo il sospetto danneggiamento di heap perché a volte ci sono strani insetti che non hanno altra spiegazione. Credo che l'opzione Rangechecking per il compilatore è solo per gli array. Voglio uno strumento che dia un'eccezione o log quando v'è una scrittura su un indirizzo di memoria che non viene allocato con l'applicazione.

Saluti

Modifica : L'errore è di tipo:

Errore: Violazione di accesso all'indirizzo 00404E78 nel modulo 'BoatLogisticsAMCAttracsServer.exe'. Leggi di indirizzo FFFFFFDD

EDIT2 : Grazie per tutti i suggerimenti. Purtroppo credo che la soluzione è più profondo di quello. Noi usiamo una versione modificata di Bold per Delphi come noi possediamo la fonte. Probabilmente ci sono alcuni errori introdotti nella framwork grassetto. Sì, abbiamo un registro con stack di chiamate che sono gestiti da JCL e anche messaggi di traccia. Quindi uno stack con l'eccezione può bloccare in questo modo:

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)

La parte interna eccezione è la stack al momento un'eccezione viene reraise.

Edit3: La teoria in questo momento è che la tabella di memoria virtuale (VMT) è in qualche modo rotto. Quando questo accade non v'è alcuna indicazione di esso. Solo quando un metodo viene chiamato viene sollevata un'eccezione ( SEMPRE sul indirizzo FFFFFFDD, -35 decimale), ma allora è troppo tardi. Non si conosce la vera causa per l'errore. Qualsiasi accenno di come catturare un bug come questo è molto apprezzato !!! Abbiamo provato con SafeMM, ma il problema è che il consumo di memoria è troppo elevata, anche quando viene utilizzato il flag 3 GB. Così ora cerco di dare una taglia per la comunità SO:)

edit4: Un suggerimento è che, secondo il registro c'è spesso (o anche sempre) un'altra eccezione prima di questo. Può essere, ad esempio il blocco ottimistico nel database. Abbiamo cercato di sollevare eccezioni con la forza, ma in ambiente di test funziona bene.

EDIT5: La storia continua ... Ho fatto una ricerca sui registri per gli ultimi 30 giorni. Il risultato:

  • "Leggi di indirizzo FFFFFFDB" 0
  • "Leggi di indirizzo FFFFFFDC" 24
  • "Leggi di indirizzo FFFFFFDD" 270
  • "Leggi di indirizzo FFFFFFDE" 22
  • "Leggi di indirizzo FFFFFFDF" 7
  • "Leggi di indirizzo FFFFFFE0" 20
  • "Leggi di indirizzo FFFFFFE1" 0

Quindi, la teoria attuale è che un enum (c'è un sacco in grassetto) sovrascrivere un puntatore. Ho avuto 5 risultati con indirizzo diverso sopra. Potrebbe significare che l'enumerazione contiene 5 valori in cui il secondo è più utilizzato. Se c'è un'eccezione un rollback dovrebbe verificarsi per il database e Boldobjects deve essere distrutta. Forse c'è una possibilità che non tutto è distrutto e un enum ancora in grado di scrivere in una posizione di indirizzo. Se questo è vero forse è possibile cercare il codice da un RegExpr per un enum con 5 valori?

EDIT6: Per riassumere, non v'è alcuna soluzione al problema ancora. Mi rendo conto che io possa trarre in inganno un po 'con lo stack di chiamate. Sì, ci sono un timer a questo, ma ci sono altri stack di chiamate senza timer. Scusa per quello. Ma ci sono 2 fattori comuni.

  • un'eccezione con Leggi di indirizzo FFFFFFxx.
  • Top di stack è System.TObject.InheritsFrom (SYS \ system.pas: 9237)

Questo mi convince che VilleK meglio descrivono il problema. Sono anche convinto che il problema è da qualche parte nel quadro grassetto. Ma la BIG La domanda è, come si può problemi come questo da risolvere? Non è sufficiente avere un Assert come VilleK suggeriscono come il danno è già avvenuto e lo stack è andato in quel momento. Quindi, per descrivere il mio punto di vista di ciò che può causare l'errore:

  1. Da qualche parte un puntatore viene assegnato un valore errato 1, ma può essere anche 0, 2, 3, ecc.
  2. Un oggetto viene assegnato a quel puntatore.
  3. Non c'è metodo di chiamata nel oggetti baseclass. Questo metodo TObject.InheritsForm motivo di essere chiamato e un'eccezione compare l'indirizzo FFFFFFDD.

Questi 3 eventi possono stare insieme nel codice, ma possono anche essere usati molto più tardi. Penso che questo sia vero per l'ultima chiamata al metodo.

EDIT7: Lavoriamo a stretto contatto con l'autore di Bold Jan Norden e di recente ha trovato un bug nel OCL-valutatore nel quadro grassetto. Quando questo è stato risolto questo tipo di eccezioni sono diminuite molto, ma ancora di tanto in tanto vengono. Ma è un grande sollievo che questo è quasi risolto.

È stato utile?

Soluzione

Non ho una soluzione, ma ci sono alcuni indizi su quel particolare messaggio di errore.

System.TObject.InheritsFrom sottrae la costante vmtParent dal Sé-puntatore (la classe) per ottenere puntatore al indirizzo della classe genitore.

In Delphi 2007 vmtParent viene definito

vmtParent = -36;

Quindi, l'errore $ FFFFFFDD (-35) suona come il puntatore di classe è di 1 in questo caso.

Ecco un banco di prova per riprodurlo:

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

L'ho provato in Delphi 2010 e ottenere 'Leggi di indirizzo FFFFFFD1' perché il vmtParent è diversa tra le versioni di Delphi.

Il problema è che questo accade dentro il quadro Bold modo si possono avere problemi guardia contro di essa nel codice dell'applicazione.

Si può provare questo sui vostri oggetti che vengono utilizzati nel DMAttracsTimers-code (che presumo sia il codice dell'applicazione):

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

Altri suggerimenti

Si scrive che si desidera che ci sia un'eccezione se

  

c'è una scrittura su un indirizzo di memoria non allocata dall'applicazione

ma che succede in ogni caso, entrambi i href="http://en.wikipedia.org/wiki/Memory_management_unit" hardware e il OS assicurarsi di questo.

Se vuoi dire si desidera controllare per la memoria non valida scrive nella gamma di indirizzi assegnati dell'applicazione, allora c'è solo così tanto si può fare. Si dovrebbe usare FastMM4 , e utilizzarlo con le sue impostazioni più verbose e paranoici in modalità di debug dell'applicazione . Questo prenderà un sacco di scrittura non valide, gli accessi al già rilasciato la memoria e come, ma non può prendere tutto. Si consideri un puntatore penzoloni che punta a un'altra posizione di memoria scrivibile (come il centro di una grande stringa o array di valori float) - scrittura ad esso avrà successo, e sarà cestino altri dati, ma non c'è modo per il gestore di memoria per la cattura di tale l'accesso.

Sembra che avete di corruzione della memoria dei dati di istanza dell'oggetto.

Il VMT in sé non è sempre corrotto, FWIW: il VMT è (normalmente) memorizzato nel file eseguibile e le pagine che mappano ad esso sono di sola lettura. Piuttosto, come dice VilleK, sembra che il primo campo dei dati di istanza nel tuo caso ha ottenuto sovrascritto con un intero a 32 bit con il valore 1. Questo è abbastanza facile da verificare: verificare i dati di istanza dell'oggetto la cui chiamata al metodo fallito, e verificare che il primo DWORD è 00000001.

Se è davvero il puntatore VMT nei dati istanza che si corrompe, ecco come avrei trovato il codice che corrompe:

  1. Assicurarsi che non v'è un modo automatico per riprodurre il problema che non richiede input da parte dell'utente. Il problema può essere riproducibile solo su una singola macchina senza riavvii tra riproduzioni a causa modo in cui Windows può scegliere di tracciare la memoria.

  2. Riprodurre il problema e prendere nota dell'indirizzo dei dati dell'istanza la cui memoria è danneggiato.

  3. Eseguire nuovamente e verificare il secondo riproduzione:. Verificare che l'indirizzo dei dati di istanza che è stato danneggiato nella seconda manche è lo stesso che l'indirizzo dalla prima esecuzione

  4. Ora, entrare in una terza prova, inserire un punto di interruzione di dati di 4 byte sulla sezione di memoria indicato dalle precedenti due piste. Il punto è di rompere su ogni modifica questa memoria. Almeno un'interruzione dovrebbe essere la chiamata TObject.InitInstance che riempie il puntatore VMT; vi possono essere altri legati alla costruzione esempio, come nel allocatore di memoria; e, nel caso peggiore, i dati pertinenti istanza possono essere stati riciclati memoria da istanze precedenti. Per ridurre la quantità di intensificare necessario, rendere i dati di log punto di interruzione lo stack di chiamate, ma in realtà non rompere. Controllando gli stack di chiamata dopo la chiamata virtuale non riesce, si dovrebbe essere in grado di trovare la cattiva scrittura.

mghie ha ragione, naturalmente. (Fastmm4 chiama il fulldebugmode bandiera o qualcosa di simile).

Si noti che che funziona normalmente con le barriere poco prima e dopo uno stanziamento di heap che vengono regolarmente controllati (su ogni accesso heapmgr?).

Questo ha due conseguenze:

  • il luogo dove FastMM rileva l'errore potrebbe deviare dal punto in cui avviene
  • una scrittura casuale totale (non troppo pieno di allocazione esistente) potrebbe non essere rilevato.

Così qui sono alcune altre cose a cui pensare:

  • Attivare runtime controllo
  • rivedere tutte le avvertenze del compilatore.
  • Prova a compilare con una versione di Delphi diverso o FPC. Altri compilatori / RTLS / heapmanagers hanno diversi layout, e che potrebbe portare ad un errore di essere catturati più facile.

Se che tutti i rendimenti nulla, prova a semplificare l'applicazione fino a che non se ne va. Poi indagare le più recenti commentati parti / ifdefed.

La prima cosa che vorrei fare è aggiungere MadExcept alla propria applicazione e ottenere una traccia dello stack che stampa l'albero vocazione precisa, che vi darà qualche idea di cosa sta succedendo qui. Invece di un'eccezione casuale e un indirizzo di memoria binaria / esadecimale, avete bisogno di vedere un albero di chiamare, con i valori di tutti i parametri e le variabili locali dalla pila.

Se ho il sospetto di corruzione della memoria in una struttura che è la chiave per la mia domanda, io spesso scrivere codice aggiuntivo per rendere il monitoraggio questo bug possibile.

Per esempio, in strutture di memoria (tipi classe o record) possono essere disposti in modo da avere un Magic1: Word all'inizio e un magic2: Word alla fine di ciascun record in memoria. Una funzione di controllo di integrità può verificare l'integrità di quelle strutture, cercando di vedere per ogni record Magic1 e magic2 non sono stati modificati da quello che sono stati fissati per nel costruttore. Il distruttore cambierebbe Magic1 e magic2 ad altri valori quali $ FFFF.

Vorrei anche prendere in considerazione l'aggiunta di tracce-registrazione per la mia domanda. la registrazione della traccia in applicazioni Delphi spesso inizia con me dichiarare un modulo TraceForm, con un TMemo in là, e il TraceForm.Trace (msg: String) funzione si avvia come "Memo1.Lines.Add (MSG)". Come la mia domanda matura, i servizi di registrazione di traccia sono il modo che guardo le applicazioni in esecuzione di modelli generali nel loro comportamento, e comportamento scorretto. Poi, quando un incidente o di danneggiamento della memoria "casuale" con "nessuna spiegazione" capita, ho un registro di traccia per tornare indietro e vedere che cosa ha portato a questo caso particolare.

A volte non è la memoria corruzione, ma semplici errori di base (ho dimenticato di controllare se è assegnato X, poi vado dereferenziarlo:. X.DoSomething (...) che si assume X è assegnato, ma non è

Ho notato che un timer è nella traccia dello stack.
Ho visto un sacco di strani errori in cui la causa è stata l'evento timer viene licenziato dopo il modulo I free'ed.
La ragione è che un evento del timer Cound essere messo sul Que messaggio e NOGE ottenere elaborati brfor la distruzione di altri componenti.
Un modo per aggirare il problema è la disabilitazione del timer come prima voce nel distruggere del modulo. Dopo aver disattivato i Application.processMessages di guardia, in modo che qualsiasi eventi timer viene lavorato prima di distruggere i componenti.
Un altro modo sta controllando se il modulo sta distruggendo nel TimerEvent. (CsDestroying in componentstate).

È possibile inserire il codice sorgente di questa procedura?

  

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

Così possiamo vedere cosa succede on line 4016.

E anche la vista CPU di questa funzione?
(Basta impostare un punto di interruzione sulla linea 4016 di questa procedura ed eseguire. E copia + incolla il contenuto Visualizza CPU se si colpisce il punto di interruzione).
Così possiamo vedere che di istruzioni della CPU è a indirizzo 00404E78.

Potrebbe esserci un problema con il codice rientrante?

Prova a mettere un po 'di codice di guardia in tutto il codice del gestore eventi TTimer:

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 @

Credo che ci sia un'altra possibilità: il timer viene sparato per verificare se ci sono "ciondolanti Logon Sessions". Poi, una chiamata è fatto su un oggetto TLogonSession per verificare se può essere eliminato (_GetMayDropSession), giusto? Ma cosa succede se l'oggetto viene distrutto già? Forse a causa di problemi di sicurezza o infilare solo una chiamata .Trasporto e non una chiamata FreeAndNil (quindi una variabile è ancora <> nil) ecc ecc Nel frattempo, vengono creati altri oggetti in modo che la memoria viene riutilizzata. Se si tenta di acces variabile qualche tempo dopo, si può / si ottiene errori casuali ...

Un esempio:

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;

Violazione di accesso all'indirizzo 004619D9 nel modulo 'Project10.exe'. Leggi di indirizzo 01010101.

Non è il problema che "_GetMayDropSession" fa riferimento a un variabile di sessione liberato?

Ho visto questo tipo di errori prima, nel TMS dove gli oggetti sono stati liberati e fa riferimento in una onchange etc (solo in alcune situazioni ha dato errori, molto difficile / impossibile da riprodurre, è fissato ora per TMS :-)). Anche con le sessioni RemObjects ho ottenuto qualcosa di simile (a causa di cattivo bug di programmazione da me).

Vorrei provare ad aggiungere una variabile dummy alla classe sessione e verificare la presenza di suo valore:

  • iMagicNumber variabile pubblica: integer;
  • costruttore di creare: iMagicNumber: = 1234567;
  • distruttore distruggere: iMagicNumber: = -1;
  • "altre procedure": assert (iMagicNumber = 1234567)
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top