6

私は、大規模なアプリケーションに Delphi 2007 を使用しているチームのメンバーです。他に説明のつかない奇妙なバグが時々あるため、ヒープの破損が疑われます。コンパイラの Rangechecking オプションは配列専用だと思います。アプリケーションによって割り当てられていないメモリ アドレスに書き込みがあった場合に、例外またはログを記録するツールが必要です。

よろしく

EDIT : エラーのタイプは次のとおりです。

エラー: モジュール 'BoatLogisticsAMCAattracsServer.exe' のアドレス 00404E78 でアクセス違反が発生しました。アドレス FFFFFFDD の読み出し

EDIT2:すべての提案をありがとう。残念ながら、解決策はそれよりも深いと思います。ソースを所有しているため、パッチを適用したバージョンの Bold for Delphi を使用しています。おそらく、Bold フレームワークで導入されたいくつかのエラーがあります。はい、JCL によって処理され、メッセージをトレースするコールスタックを含むログがあります。したがって、例外のあるコールスタックは次のようにロックできます。

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)

内部例外部分は、例外が再発生した瞬間のコールスタックです。

EDIT3:現在の理論は、仮想メモリテーブル(VMT)が何らかの形で壊れているというものです。これが発生した場合、その兆候はありません。メソッドが呼び出されたときにのみ例外が発生します (アドレス FFFFFFDD でALWAYS、10 進数で -35) が、それでは遅すぎます。エラーの本当の原因がわからない。このようなバグをキャッチする方法のヒントは本当にありがたいです!!! SafeMM で試してみましたが、3 GB フラグを使用してもメモリ消費量が多すぎることが問題です。だから今、私はSOコミュニティに報奨金を与えようとしています:)

EDIT4: 1つのヒントは、ログによると、この前に別の例外がしばしば(または常に)あるということです。たとえば、データベースの楽観的ロックなどです。強制的に例外を発生させようとしましたが、テスト環境では問題なく動作します。

EDIT5:話は続きます... 過去 30 日間のログを検索しました。結果:

  • 「アドレス FFFFFFDB の読み出し」 0
  • 「アドレス FFFFFFDC の読み出し」 24
  • 「アドレス FFFFFFDD の読み取り」 270
  • 「アドレス FFFFFFDE の読み出し」 22
  • 「アドレス FFFFFFDF の読み出し」 7
  • 「アドレス FFFFFFE0 の読み出し」 20
  • 「アドレス FFFFFFE1 の読み出し」 0

したがって、現在の理論では、列挙型 (ボールド体にたくさんあります) がポインターを上書きします。上記の異なるアドレスで 5 件ヒットしました。列挙型が 5 つの値を保持し、2 番目の値が最も使用されていることを意味する可能性があります。例外が発生した場合、データベースのロールバックが発生し、Boldobjects が破棄される必要があります。おそらく、すべてが破棄されるわけではなく、列挙型がまだアドレスの場所に書き込むことができる可能性があります。これが本当なら、5 つの値を持つ列挙型の正規表現でコードを検索することは可能でしょうか?

EDIT6:要約すると、問題の解決策はまだありません。コールスタックで少し誤解を招く可能性があることは承知しています。はい、タイマーがありますが、タイマーのない他のコールスタックがあります。そのために残念。しかし、2つの共通点があります。

  • アドレス FFFFFFxx の読み取りによる例外。
  • コールスタックのトップは System.TObject.InheritsFrom (sys\system.pas:9237) です。

これは、 VilleKが問題を最もよく説明していることを確信させてくれます。また、問題は Bold フレームワークのどこかにあると確信しています。しかし、大きな問題は、このような問題をどのように解決できるかということです。VilleKのような Assert を提案するだけでは十分ではありません。損傷は既に発生しており、その時点でコールスタックはなくなっているからです。したがって、エラーの原因についての私の見解を説明するには、次のようにします。

  1. どこかでポインターに不適切な値 1 が割り当てられていますが、0、2、3 などになることもあります。
  2. オブジェクトがそのポインターに割り当てられます。
  3. オブジェクトの基底クラスにメソッド呼び出しがあります。これにより、メソッド TObject.InheritsForm が呼び出され、アドレス FFFFFFDD に例外が発生します。

これら 3 つのイベントはコード内で一緒に使用できますが、後で使用することもできます。これは最後のメソッド呼び出しにも当てはまると思います。

EDIT7:私たちは Bold Jan Norden の作者と緊密に協力しており、彼は最近、Bold フレームワークの OCL エバリュエーターにバグを発見しました。これが修正されたとき、これらの種類の例外は大幅に減少しましたが、それでも時々発生します。しかし、これがほぼ解決されたことは大きな安堵です。

4

10 に答える 10

6

次の場合に例外を設けたいと書いています。

アプリケーションによって割り当てられていないメモリアドレスへの書き込みがあります

とにかくそれは起こります、ハードウェアOSの両方がそれを確認します.

アプリケーションの割り当てられたアドレス範囲で無効なメモリ書き込みをチェックしたい場合、できることは限られています。FastMM4を使用し、アプリケーションのデバッグ モードで最も冗長でパラノイドな設定で使用する必要があります。これにより、多くの無効な書き込み、すでに解放されたメモリへのアクセスなどをキャッチできますが、すべてをキャッチすることはできません。別の書き込み可能なメモリ位置 (大きな文字列や float 値の配列の中間など) を指すダングリング ポインターを考えてみましょう。書き込みは成功し、他のデータは破壊されますが、メモリ マネージャーがそのような場所をキャッチする方法はありません。アクセス。

于 2009-12-10T10:41:20.293 に答える
5

解決策はありませんが、その特定のエラー メッセージに関するいくつかの手がかりがあります。

System.TObject.InheritsFrom は、自己ポインタ (クラス) から定数 vmtParent を減算して、親クラスのアドレスへのポインタを取得します。

Delphi 2007 では、vmtParent が定義されています。

vmtParent = -36;

したがって、エラー $FFFFFFDD (-35) は、この場合、クラス ポインターが 1 のように聞こえます。

これを再現するテストケースは次のとおりです。

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

Delphi 2010 で試してみたところ、Delphi のバージョン間で vmtParent が異なるため、「アドレス FFFFFFD1 の読み取り」が表示されました。

問題は、これが Bold フレームワークの奥深くで発生するため、アプリケーション コードでこれを防ぐのに苦労する可能性があることです。

DMAtracsTimers-code で使用されているオブジェクトでこれを試すことができます (これはアプリケーション コードであると想定しています)。

Assert(Integer(Obj.ClassType)<>1,'Corrupt vmt');
于 2010-02-01T08:40:15.863 に答える
3

オブジェクトインスタンスデータのメモリが破損しているようです。

VMT自体は破損していません。FWIW:VMTは(通常)実行可能ファイルに保存され、VMTにマップされるページは読み取り専用です。むしろ、VilleKが言うように、ケースのインスタンスデータの最初のフィールドが値1の32ビット整数で上書きされたように見えます。これは簡単に確認できます。メソッド呼び出しが失敗したオブジェクトのインスタンスデータを確認します。最初のdwordが00000001であることを確認します。

破損しているのがインスタンスデータ内のVMTポインターである場合、それを破損しているコードを見つける方法は次のとおりです。

  1. ユーザー入力を必要としない問題を再現する自動化された方法があることを確認してください。この問題は、Windowsがメモリのレイアウトを選択する方法が原因で、複製の間に再起動せずに1台のマシンでのみ再現できる場合があります。

  2. 問題を再現し、メモリが破損しているインスタンスデータのアドレスをメモします。

  3. 再実行して2回目の再現を確認します。2回目の実行で破損したインスタンスデータのアドレスが、最初の実行のアドレスと同じであることを確認します。

  4. 次に、3回目の実行に進み、前の2回の実行で示されたメモリのセクションに4バイトのデータブレークポイントを設定します。重要なのは、このメモリへのすべての変更を中断することです。少なくとも1つのブレークは、VMTポインターを埋めるTObject.InitInstance呼び出しである必要があります。メモリアロケータなど、インスタンスの構築に関連するものが他にもある可能性があります。最悪の場合、関連するインスタンスデータが以前のインスタンスからメモリをリサイクルされた可能性があります。必要なステッピングの量を減らすには、データブレークポイントでコールスタックをログに記録しますが、実際にはブレークしません。仮想呼び出しが失敗した後に呼び出しスタックをチェックすることにより、不正な書き込みを見つけることができるはずです。

于 2010-02-01T09:40:58.627 に答える
2

最初に行うことは、MadExcept をアプリケーションに追加して、正確な呼び出しツリーを出力するスタック トレースバックを取得することです。これにより、ここで何が起こっているかがわかります。ランダムな例外と 2 進数/16 進数のメモリ アドレスの代わりに、スタックからのすべてのパラメーターとローカル変数の値を含む呼び出しツリーを確認する必要があります。

アプリケーションにとって重要な構造でメモリ破損が疑われる場合、このバグを追跡できるように追加のコードを記述することがよくあります。

たとえば、メモリ構造 (クラスまたはレコード タイプ) は、メモリ内の各レコードの先頭に Magic1:Word を配置し、最後に Magic2:Word を配置することができます。整合性チェック関数は、各レコードの Magic1 と Magic2 がコンストラクタで設定されたものから変更されていないことを確認することで、これらの構造の整合性をチェックできます。デストラクタは、Magic1 と Magic2 を $FFFF などの他の値に変更します。

また、アプリケーションにトレース ロギングを追加することも検討します。Delphi アプリケーションでのトレース ロギングは、多くの場合、TMemo を含む TraceForm フォームを宣言することから始まります。TraceForm.Trace(msg:String) 関数は "Memo1.Lines.Add(msg)" として開始されます。私のアプリケーションが成熟するにつれて、トレース ロギング機能は、アプリケーションの動作の全体的なパターンや誤動作について、実行中のアプリケーションを監視する方法です。次に、「説明のない」「ランダムな」クラッシュまたはメモリの破損が発生した場合、トレース ログを調べて、この特定のケースの原因を確認します。

メモリの破損ではなく、単純な基本的なエラーである場合があります (X が割り当てられているかどうかを確認するのを忘れていたので、逆参照します: X が割り当てられていると想定している X.DoSomething(...) ですが、そうではありません。

于 2009-12-10T18:47:05.677 に答える
2

もちろん、mghieは正しいです。(fastmm4 はフラグ fulldebugmode などを呼び出します)。

これは通常、定期的にチェックされるヒープ割り当ての直前と直後のバリアで機能することに注意してください (すべての heapmgr アクセスで?)。

これには 2 つの結果があります。

  • fastmm がエラーを検出する場所は、エラーが発生した場所とは異なる可能性があります。
  • 完全なランダム書き込み (既存の割り当てのオーバーフローではない) が検出されない可能性があります。

そこで、他にも次のことを考えてみてください。

  • 実行時チェックを有効にする
  • コンパイラのすべての警告を確認してください。
  • 別の Delphi バージョンまたは FPC でコンパイルしてみてください。他のコンパイラー/rtls/ヒープマネージャーは異なるレイアウトを持っているため、エラーがより簡単に検出される可能性があります。

それでも何も得られない場合は、アプリケーションがなくなるまで単純化してみてください。次に、最新のコメント/ifdef 部分を調査します。

于 2009-12-10T12:46:34.300 に答える
1

スタック トレースにタイマーがあることに気付きました。
フォームを解放した後にタイマーイベントが発生したことが原因である奇妙なエラーがたくさん見られました。
その理由は、タイマー イベントがメッセージ que に置かれる可能性があり、他のコンポーネントの破棄のために野毛が処理される可能性があるためです。
この問題を回避する 1 つの方法は、フォームの破棄の最初のエントリとしてタイマーを無効にすることです。タイム コールを無効にした後、Application.processMessages を呼び出して、コンポーネントを破棄する前にすべてのタイマー イベントを処理します。
もう 1 つの方法は、timerevent でフォームが破棄されているかどうかを確認することです。(コンポーネント状態で csDestroying)。

于 2010-02-01T10:18:06.093 に答える
0

再入可能コードに問題がありますか?

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 @

于 2010-02-02T00:06:18.140 に答える
0

別の可能性があると思います。「ダングリングログオンセッション」があるかどうかを確認するためにタイマーが起動されます。次に、TLogonSession オブジェクトに対して呼び出しが行われ、ドロップされる可能性があるかどうかが確認されます (_GetMayDropSession) ですね。しかし、オブジェクトがすでに破棄されている場合はどうなるでしょうか? おそらく、スレッドの安全性の問題、または FreeAndNil 呼び出しではなく .Free 呼び出し (変数がまだ <> nil である) などが原因である可能性があります。それまでの間、メモリが再利用されるように他のオブジェクトが作成されます。しばらくして変数にアクセスしようとすると、ランダムエラーが発生する可能性があります...

例:

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;

モジュール 'Project10.exe' のアドレス 004619D9 でアクセス違反が発生しました。アドレス 01010101 の読み取り。

于 2010-02-03T07:13:19.217 に答える
0

「_GetMayDropSession」が解放されたセッション変数を参照しているのが問題ではないでしょうか?

オブジェクトが解放され、onchange などで参照された TMS で、この種のエラーを見たことがあります (一部の状況でのみエラーが発生し、再現が非常に困難/不可能でしたが、現在は TMS によって修正されています :-) )。また、RemObjects セッションでも同様の結果が得られました (自分自身のプログラミングのバグが原因で)。

セッション クラスにダミー変数を追加して、その値を確認してみます。

  • パブリック変数 iMagicNumber: 整数;
  • コンストラクタ作成: iMagicNumber := 1234567;
  • デストラクタ destroy: iMagicNumber := -1;
  • 「その他の手順」: assert(iMagicNumber = 1234567)
于 2010-02-08T07:31:19.357 に答える
0

この手順のソースコードを投稿できますか?

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

4016 行目で何が起こっているかがわかります。

また、この関数の CPU ビューは?
(この手順の 4016 行目にブレークポイントを設定して実行するだけです。ブレークポイントにヒットした場合は、CPU ビューの内容をコピーして貼り付けます)。
したがって、どの CPU 命令がアドレス 00404E78 にあるかがわかります。

于 2010-02-01T19:17:50.233 に答える