
自分の DLL をデバッグするためのクラッシュログの利用方法
MetaTrader 4
—
例
| 16 2月 2016, 12:04
MetaTrader 4 クライアントターミナルにはターミナル動作中に発生するエラー条件を検出し、そのようなエラーが報告されるクラッシュログを作成する統合された方法があります。作成されたレポートは logs\crashlog.log ファイルに格納され、クライアントターミナルの次回起動時にトレードサーバーに送信されます。エラー条件レポートにはユーザーの個人情報詳細はなく、クライアントターミナル内のエラーの位置を突き止めるシステムデータのみ含まれます。この情報は、重大なエラー修正に使用されるため、製造者にとってはひじょうに重要なものなのです。そして開発されたソフトウェアはさらクラッシュプルーフとなります。
ユーザーから受け取った全クラッシュログ中25~30% はカスタム dll からインポートされた関数が実行されるときにおこるエラーが原因のようです。この情報はどのみちクライアントターミナルの作成者の役には立ちませんが、トラブルシューティングで対応する dll の作成者の役に立つかもしれません。どのようにエラーレポートからのデータが使われるのかお見せします。 ExpertSample.dll および ExportFunctions と呼ばれる例experts\samples ディレクトリにある mq4 が基礎となっています。

以下がエラーレポートのテキスト完全版です。
Time : 2006.07.12 14:43 Program : Client Terminal Version : 4.00 (build: 195, 30 Jun 2006) Owner : MetaQuotes Software Corp. (MetaTrader) OS : Windows XP Professional 5.1 Service Pack 2 (Build 2600) Processors : 2, type 586, level 15 Memory : 2095848/1727500 kb Exception : C0000005 Address : 77C36FA3 Access Type : read Access Addr : 00000000 Registers : EAX=000000FF CS=001b EIP=77C36FA3 EFLGS=00010202 : EBX=FFFFFFFF SS=0023 ESP=024DFABC EBP=024DFAC4 : ECX=0000003F DS=0023 ESI=00000000 FS=003b : EDX=00000003 ES=0023 EDI=10003250 GS=0000 Stack Trace : 10001079 0045342E 0045D627 004506EC : 7C80B50B 00000000 00000000 00000000 : 00000000 00000000 00000000 00000000 : 00000000 00000000 00000000 00000000 Modules : 1 : 00400000 00292000 C:\Program Files\MetaTrader 4\terminal.exe 2 : 10000000 00005000 C:\Program Files\MetaTrader 4\experts\libraries\ExpertSample.dll ... .......................................................... 35 : 7C9C0000 00819000 C:\WINDOWS\system32\SHELL32.dll Call stack : 77C36F70:0033 [77C36FA3] memcpy [C:\WINDOWS\system32\msvcrt.dll] 10001051:0028 [10001079] GetStringValue [C:\Program Files\MetaTrader 4\experts\libraries\ExpertSample.dll] 00452DD0:065E [0045342E] ?CallDllFunction@CExpertInterior 00459AC0:3B67 [0045D627] ?ExecuteStaticAsm@CExpertInterior 004505E0:010C [004506EC] ?RunExpertInt@CExpertInterior 7C80B357:01B4 [7C80B50B] GetModuleFileNameA [C:\WINDOWS\system32\kernel32.dll]
何が起こったのでしょうか?
- Exception : C0000005 は「アクセス違反」により発生したエラー条件を意味します。
- Access Type : read は読み込みの試みがあったことを意味します。
- Acess Addr : 00000000 は、アウトオブプロセスメモリがゼロアドレスであることを意味します。
ここで呼び出しスタックを見ます。
77C36FA3 のアドレスはスタックの一番上のアドレスと同じです。これは、あるメモリ領域から別のメモリ領域に内容をコピーする memcpy 関数の実行中にエラーが発生したことを意味します。そこで、ゼロアドレスのメモリ領域からデータをコピーする試みがあったかどうか、かなりの確実で判断することができます。
呼び出しスタックの 2 行目はどの関数が誤ったパラメータで memcpy 関数を呼び出しのかを知らせます。これは ExpertSample.dll という名前のライブラリからの GetStringValue 関数です。
この関数のソースコードを見ます。
memcpy 関数は上記関数によって一度だけ呼び出されていることがわかります。1番目のパラメータは temp_string 変数が書き込まれている既存のメモリ領域を指しているので、誤っているのは2番目のパラメータであると結論づけることができます。実際、提示されている例では 0 に対する変数チェックは行われていません。行 if(spar==NULL) はクラッシュからわれわれを保護してくれています。
分析した関数内で memcpy 関数が2度以上呼び出されていたらどうすればよいのでしょうか?われわれのプロジェクト設定では、コンパイルのもっとも細かいリストの出力を設定します。

プロジェクトが再構築されたら、各ソースの .cpp ファイルすべてに対して拡張子 .cod を持つファイルリストを取得することとなります。ここで注目するのは ExpertSample. cod ですが、GetStringValue 関数に対して取得されるコードの一部だけです。以下がそれです。
それでは、インポートされた関数のすぐ内側でエラーが発生するケースを詳しく見ます。コードを修正します。
memcpy 関数呼び出しを独自のバイト単位のデータコピーループと置き換えました。ただし、エラー条件とエラーレポート作成のために 0 チェックはしませんでした。新しいレポートでは、呼び出しスタックはやや違っています
最後は、インポートされた関数に対してゼロ表示をどのように渡したかということに関する2行です。
初期化されていない文字列をパラメータとして渡しました。初期化されていない文字列には注意します。つねに NULL に対して受信表示を確認し、クラッシュをなるべく少なくするようにします。
77C36FA3 のアドレスはスタックの一番上のアドレスと同じです。これは、あるメモリ領域から別のメモリ領域に内容をコピーする memcpy 関数の実行中にエラーが発生したことを意味します。そこで、ゼロアドレスのメモリ領域からデータをコピーする試みがあったかどうか、かなりの確実で判断することができます。
呼び出しスタックの 2 行目はどの関数が誤ったパラメータで memcpy 関数を呼び出しのかを知らせます。これは ExpertSample.dll という名前のライブラリからの GetStringValue 関数です。
この関数のソースコードを見ます。
__declspec(dllexport) char* __stdcall GetStringValue(char *spar) { static char temp_string[256]; //---- printf("GetStringValue takes \"%s\"\n",spar); memcpy(temp_string,spar,sizeof(temp_string)-1); temp_string[sizeof(temp_string)-1]=0; //---- return(temp_string); }
memcpy 関数は上記関数によって一度だけ呼び出されていることがわかります。1番目のパラメータは temp_string 変数が書き込まれている既存のメモリ領域を指しているので、誤っているのは2番目のパラメータであると結論づけることができます。実際、提示されている例では 0 に対する変数チェックは行われていません。行 if(spar==NULL) はクラッシュからわれわれを保護してくれています。
分析した関数内で memcpy 関数が2度以上呼び出されていたらどうすればよいのでしょうか?われわれのプロジェクト設定では、コンパイルのもっとも細かいリストの出力を設定します。

プロジェクトが再構築されたら、各ソースの .cpp ファイルすべてに対して拡張子 .cod を持つファイルリストを取得することとなります。ここで注目するのは ExpertSample. cod ですが、GetStringValue 関数に対して取得されるコードの一部だけです。以下がそれです。
?GetStringValue@@YGPADPAD@Z PROC NEAR ; GetStringValue ; 70 : { 00051 55 push ebp 00052 8b ec mov ebp, esp ; 71 : static char temp_string[256]; ; 72 : //---- ; 73 : printf("GetStringValue takes \"%s\"\n",spar); 00054 8b 45 08 mov eax, DWORD PTR _spar$[ebp] 00057 50 push eax 00058 68 00 00 00 00 push OFFSET FLAT:$SG19680 0005d ff 15 00 00 00 00 call DWORD PTR __imp__printf 00063 83 c4 08 add esp, 8 ; 74 : memcpy(temp_string,spar,sizeof(temp_string)-1); 00066 68 ff 00 00 00 push 255 ; 000000ffH 0006b 8b 4d 08 mov ecx, DWORD PTR _spar$[ebp] 0006e 51 push ecx 0006f 68 00 00 00 00 push OFFSET FLAT:_?temp_string@?1??GetStringValue@@YGPADPAD@Z@4PADA 00074 e8 00 00 00 00 call _memcpy 00079 83 c4 0c add esp, 12 ; 0000000cH ; 75 : temp_string[sizeof(temp_string)-1]=0; 0007c c6 05 ff 00 00 00 00 mov BYTE PTR _?temp_string@?1??GetStringValue@@YGPADPAD@Z@4PADA+255, 0 ; 76 : //---- ; 77 : return(temp_string); 00083 b8 00 00 00 00 mov eax, OFFSET FLAT:_?temp_string@?1??GetStringValue@@YGPADPAD@Z@4PADA ; 78 : } 00088 5d pop ebp 00089 c2 04 00 ret 4 ?GetStringValue@@YGPADPAD@Z ENDP ; GetStringValue呼び出しスタックの2行目の数字 10001051:0028 は GetStringValue 関数内のアドレスを表します。関数が入れられると、呼び出しスタック内の上記行が実行され、制御はこのアドレスに渡されます。オブジェクトコードでは、GetStringValue 関数はアドレスからスタートします(アドレスは16進法で表記されることに留意します)。この値に0028 を追加し、アドレスが 00079 となるようにします。このアドレスでは、add esp,12 指令が置かれ、それは memcpy 関数呼び出し指令の直後に続きます。われわれはこの場所を見出しました。
それでは、インポートされた関数のすぐ内側でエラーが発生するケースを詳しく見ます。コードを修正します。
__declspec(dllexport) char* __stdcall GetStringValue(char *spar) { static char temp_string[256]; //---- printf("GetStringValue takes \"%s\"\n",spar); for(int i=0; i<sizeof(temp_string)-1; i++) { temp_string[i]=spar[i]; if(spar[i]==0) break; } temp_string[sizeof(temp_string)-1]=0; //---- return(temp_string); }
memcpy 関数呼び出しを独自のバイト単位のデータコピーループと置き換えました。ただし、エラー条件とエラーレポート作成のために 0 チェックはしませんでした。新しいレポートでは、呼び出しスタックはやや違っています
Call stack : 10001051:003A [1000108B] GetStringValue [C:\Program Files\MetaTrader 4\experts\libraries\ExpertSample.dll] 00452DD0:065E [0045342E] ?CallDllFunction@CExpertInterior 00459AC0:3B67 [0045D627] ?ExecuteStaticAsm@CExpertInterior 004505E0:010C [004506EC] ?RunExpertInt@CExpertInterior 7C80B357:01B4 [7C80B50B] GetModuleFileNameA [C:\WINDOWS\system32\kernel32.dll]エラーは GetStringValue 関数のアドレス 003A で発生しています。生成されたリストを見ます。
?GetStringValue@@YGPADPAD@Z PROC NEAR ; GetStringValue ; 70 : { 00051 55 push ebp 00052 8b ec mov ebp, esp 00054 51 push ecx ; 71 : static char temp_string[256]; ; 72 : //---- ; 73 : printf("GetStringValue takes \"%s\"\n",spar); 00055 8b 45 08 mov eax, DWORD PTR _spar$[ebp] 00058 50 push eax 00059 68 00 00 00 00 push OFFSET FLAT:$SG19680 0005e ff 15 00 00 00 00 call DWORD PTR __imp__printf 00064 83 c4 08 add esp, 8 ; 74 : for(int i=0; i<sizeof(temp_string)-1; i++) 00067 c7 45 fc 00 00 00 00 mov DWORD PTR _i$[ebp], 0 0006e eb 09 jmp SHORT $L19682 $L19683: 00070 8b 4d fc mov ecx, DWORD PTR _i$[ebp] 00073 83 c1 01 add ecx, 1 00076 89 4d fc mov DWORD PTR _i$[ebp], ecx $L19682: 00079 81 7d fc ff 00 00 00 cmp DWORD PTR _i$[ebp], 255 ; 000000ffH 00080 73 22 jae SHORT $L19684 ; 76 : temp_string[i]=spar[i]; 00082 8b 55 08 mov edx, DWORD PTR _spar$[ebp] 00085 03 55 fc add edx, DWORD PTR _i$[ebp] 00088 8b 45 fc mov eax, DWORD PTR _i$[ebp] 0008b 8a 0a mov cl, BYTE PTR [edx] 0008d 88 88 00 00 00 00 mov BYTE PTR _?temp_string@?1??GetStringValue@@YGPADPAD@Z@4PADA[eax], cl ; 77 : if(spar[i]==0) break; 00093 8b 55 08 mov edx, DWORD PTR _spar$[ebp] 00096 03 55 fc add edx, DWORD PTR _i$[ebp] 00099 0f be 02 movsx eax, BYTE PTR [edx] 0009c 85 c0 test eax, eax 0009e 75 02 jne SHORT $L19685 000a0 eb 02 jmp SHORT $L19684 $L19685: ; 78 : } 000a2 eb cc jmp SHORT $L19683 $L19684: ; 79 : temp_string[sizeof(temp_string)-1]=0; 000a4 c6 05 ff 00 00 00 00 mov BYTE PTR _?temp_string@?1??GetStringValue@@YGPADPAD@Z@4PADA+255, 0 ; 80 : //---- ; 81 : return(temp_string); 000ab b8 00 00 00 00 mov eax, OFFSET FLAT:_?temp_string@?1??GetStringValue@@YGPADPAD@Z@4PADA ; 82 : } 000b0 8b e5 mov esp, ebp 000b2 5d pop ebp 000b3 c2 04 00 ret 4 ?GetStringValue@@YGPADPAD@Z ENDP ; GetStringValue最初のアドレスは同じ 00051 です。003A を追加し、アドレス 0008B を取得します。このアドレスに mov cl, BYTE PTR [edx] 指令が置かれます。レポートでレジスタの内容を見ます。
Registers : EAX=00000000 CS=001b EIP=1000108B EFLGS=00010246 : EBX=FFFFFFFF SS=0023 ESP=0259FAD4 EBP=0259FAD8 : ECX=77C318BF DS=0023 ESI=018ECD80 FS=003b : EDX=00000000 ES=0023 EDI=000000E8 GS=0000もちろん EDX レジスタはゼロを含んでいます。アウトオブプロセスメモリにアクセスし、クラッシュしました。
最後は、インポートされた関数に対してゼロ表示をどのように渡したかということに関する2行です。
string null_string; string sret=GetStringValue(null_string);
初期化されていない文字列をパラメータとして渡しました。初期化されていない文字列には注意します。つねに NULL に対して受信表示を確認し、クラッシュをなるべく少なくするようにします。
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/1414
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
ディスカッションに移動

楽になる21の方法:MetaTrader 4 クライアントターミナルの機潜在的機能フルスクリーン、ホットキー、高速検索バー、ウィンドウ最小化、お気に入り、トラフィック削減、ニュース無効化、シンボル設定、銘柄リスト、検証用テンプレートと個別チャート、プロフィール、クロスヘア、電子ルーラー、バー単位のチャートページめくり、チャート内アカウント履歴、未決注文タイプ、ストップロスおよびテイクプロフィットの修正、削除の取り消し、チャート表示。

初心者プログラマが「金のなる木」を作成させる最も頻度の高い失敗が調査されました。テスターではすばらしい結果を示したが、実際のトレーディングでは損失を出したエキスパートが示されています。

ターミナルや自分のアカウントで何が起こっているのかモニターを絶えず見なくても気づく方法システムイベント、カスタムイベント、wave および実行可能ファイル、電子メッセージ、SMTP サーバーアクセスの設定、パブリケーション、FTP サーバーアクセスの設定。

この記事は、Nullバーが変化した際に MetaTrader 4クライアントターミナルのインジケーター値の再計算に関する問題に焦点を当てています。複数の再計算前に保存されたプログラムコードを保存させるいくつかの追加プログラムをインジケーターに追加する方法について述べています。

取引の機会を逃しています。
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索