しかし、ヒーローはインストールされており、プレイしたいという願望があります。 私はこのキャンペーンを実施することにしました(残念なことに、過去にそれを経験する忍耐を持っていませんでした)。 立ち上げ、エラシアの復活キャンペーンを選択し、エピソード「Victims of War」をクリックしました。 そして、あなたがプログラミングに従事している間、直観がおそらくしばらくの間現れることはわかりません。 一般的に、ヒーローはこの写真を見せてくれました:
そして、私によくあることですが、ゲームオーバーになり、より興味深くエキサイティングなレッスンが始まりました。
デバッガで起動し、非常に問題のあるアイテムを選択します。 行mov bl、[eax + ecx + 1F879h]でメモリにアクセスすると、コードのこのセクションで例外が発生します。
。 テキスト: 004567A3 mov ecx 、 dword_68C818
。 テキスト: 004567A9 test ecx 、 ecx
。 テキスト: 004567AB jl short loc_4567F2
。 テキスト: 004567AD mov dl 、 [ eax + edi + 1F879h ]
。 テキスト: 004567B4 mov bl 、 [ eax + ecx + 1F879h ]
レジスタの値を確認します。EDIはゼロ、EAXはメモリの正しいセクションを指し、ECXは巨大でどこも指し示していません。 おそらく、問題はそこにあります。 ここで、値が入力されている場所を見つける必要があります。 コードを少し上に移動すると、mov ecx、dword_68C818という行が見つかります。 ええ、それはECXレジスタがグローバル変数から生成されることを意味します。 相互参照ウィンドウを開いて、この変数に値が書き込まれる場所を確認します。
興味深いことに、値は同じ関数でそれに書き込まれます。 一般的に奇妙なこと。 変数はグローバルであり、1つの関数でのみ使用されます。 スタック上に作成する必要があるようです? まあ、私は何かを理解していないと思います。 誰が書いているのか見ます。 そして、それは次のようになります:
。 テキスト: 00456736 mov eax 、 [ eax + 1F468h ] ; これは議論です
。 テキスト: 0045673F push eax
。 テキスト: 00456743 mov ecx 、 [ ecx + 0A4h ] ; ここに「悪い」値を持つオブジェクトがあります
。 テキスト: 00456749 mov edx 、 [ ecx ] ; これは彼の仮想関数へのポインタの表です
。 テキスト: 0045674B call dword ptr [ edx + 20h ] ; ここでサブルーチンを呼び出します
。 テキスト: 00483A60 push ebp
。 テキスト: 00483A61 mov ebp 、 esp
。 テキスト: 00483A63 mov eax 、 [ ecx + 8 ]
。 テキスト: 00483A66 mov ecx 、 [ ebp + arg_0 ] ; この引数は-1です
。 テキスト: 00483A69 mov eax 、 [ eax + ecx * 8 ] ; ここでresult = arr [-1]のようなことが起こります。 非常に不審な場所
。 テキスト: 00483A6D pop ebp
。 テキスト: 00483A6E retn 4
。 テキスト: 0045674E mov dword_68C818 、 eax ; ここで、ルーチンが返した値をグローバル変数に書き込みます。 これは同じ「悪い」数字で、ゲームをダウンさせます。
配列が負のインデックスでアクセスされるのは非常に奇妙です。 引数にエラーがあると想定できます。 テキスト検索により、1F468hのオフセットを持つ変数へのアクセスを探しています
どうやら、彼らは彼女にちょうど2回目を向けます。 初期化中に定数が初めて書き込まれたとき(明らかにこの定数は私に届きます)。 2番目のケースは、より詳細に検討する必要があります。 検索結果に示されているアドレスに移動します(関数は大きいため、スクリーンショットは表示しません)。ブレークポイントを設定し、スクリプトを選択します。 何も起きていません。 関数の先頭まで進みます。 この関数は非常に頻繁に呼び出されます。 多くのブランチが存在することにより、これが一部のメッセージの処理であると想定できます。 スクリプトウィンドウにあるすべてのボタンをクリックしてみます。 ブレークポイントは、最初の賞品の選択中にトリガーされます。 写真のように、このようなもの:
今、あなたは人間的に間違いを犯すことができます。 初期ボーナスを返す仮想関数getStartupBonusがあります。 あるタイプのシナリオでは、変数が返され、別のスクリプトでは、外部インデックスによって指定された配列要素が返されます。 インデックスは配列の範囲を超えないことが理解されます。 しかし、何らかの理由で、スクリプトをロードする前に、間違った値がインデックスに書き込まれます。 配列の外側で、アプリケーションがクラッシュします。
いくつかの解決策があります。 たとえば、変数をゼロに初期化する定数を変更できます。 しかし、実際には、これは完全に予期しない結果につながる可能性があります。 配列を操作する関数の前に、引数をチェックするいくつかの命令を追加するのが最善であると思われます。 そして、関数は次のようになります。
。 テキスト: 0048385A push ebp
。 テキスト: 0048385B mov ebp 、 esp ; ebpはスタックを指します
。 テキスト: 0048385D mov eax 、 [ ecx + 8 ] ; eaxは配列を指します
。 テキスト: 00483860 mov ecx 、 [ ebp + arg_0 ] ; 引数には、予想される配列のインデックスが含まれます
。 テキスト: 00483863 テスト ecx 、 ecx ; インデックスが負であるかどうかを確認します
。 テキスト: 00483865 jns short loc_483869 ; そうでない場合は、配列要素の読み取りに進みます
。 テキスト: 00483867 xor ecx 、 ecx ; 配列のインデックスが負の場合、ゼロに等しいとみなします
。 テキスト: 00483869 mov eax 、 [ eax + ecx * 8 ] ; 配列の要素を返します
。 テキスト: 0048386C pop ebp
。 テキスト: 0048386D retn 4
変更を実行可能ファイルに書き込み、ゲームを再起動し、キャンペーンを選択すると、動作します。