Verwendung der Crashlogs, um eigene dll einzurichten
Der Client-Terminal MetaTrader4 hat ein eingebautes Mittel, das die Ausnahmesituationen (Fehlerzustand) erkennt, die während der Arbeit auftreten und Terminal wird Crashlogs über solche Situationen erzeugen. Das erzeugte Crashlog wird in der Datei logs\crashlog.log gespeichert, die zum Handel-Server beim nächsten Start des Handel-Terminals gesendet wird. Beachten Sie bitte, dass keine persönliche Daten im Crashlog gibt, sondern nur die Systeminformationen, welche die Fehlerstelle im Client-Terminal lokalisieren können. Diese Information ist für die Entwickler sehr wichtig, denn es ermöglicht kritische Fehler zu korrigieren und dadurch die entwickelte Software noch zuverlässiger zu erstellen.
25-30% von allen Crashlogs, die vom Benutzer kommen, entstehen durch die Ausführungsfehler der Funktion, die aus dem benutzerdefinierten dll importiert werden. Diese Angaben werden Entwicklern des Terminals nicht helfen, aber es kann den dll-Entwicklern helfen, die auf der Suche nach diesen Fehlern sind. Wir zeigen, wie man Informationen aus Fehlerbericht benutzen kann. Als Beispiel werden ExpertSample.dll und ExportFunctions gegeben. mq4, die man im Verzeichnis experts\samples finden kann.
Unten ist der ganze Fehlerberichtstext:
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]
Was ist passiert?
- Exception : C0000005 bedeutet eine Ausnahmesituation, die wegen dem Zugriff zum "fremden" oder ungeteilten Speicherbereich (Access Violation) geworden ist.
- Access Type : read heißt, dass es einen Versuch gab, zu lesen.
- Acess Addr : 00000000 heißt, dass der "fremde" Speicherbereich eine Nulladresse hat.
Die Adresse 77C36FA3 ist gleich mit der Adresse, die in der Oberseite der Aufruf-Liste ist. Das heißt, dass der Fehler während der Ausführung der Funktion memcpy aufgetreten ist, diese Funktion kopiert den Inhalt eines Speicherbereichs zu einem anderen. Dabei kann man mit großer Sicherheit beurteilen, dass es versucht wurde, Daten aus dem Speicherbereich zu kopieren, der eine Nulladresse hat.
Die zweite Zeile der Aufruf-Liste informiert uns über die Funktion, welche die Funktion memcpy mit falschen Parametern aufgerufen hat. Das ist GetStringValue aus der Bibliothek ExpertSample.dll.
Schauen wir auf den Anfangscode dieser Funktion:
__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); }
Wir können hier sehen, dass die Funktion memcpy in der Funktion nur einmal aufgerufen wird. Da der erste Parameter auf den vorhandenen Speicherbereich hinweist, der von der Variable temp_string besetzt ist, kann man schließen, dass der zweite Parameter den Fehler verursacht. Tatsächlich gibt es im gegebenen Beispiel keine Überprüfung der Variable spar um 0. Die Zeile if(spar==NULL) würde uns vor dem Crash schützen.
Also, was soll gemacht werden, wenn es bei der Testfunktion ein paar Aufrufen der Funktion memcpy wären? In den Projekt-Einstellungen unserer dll stellen wir die Lieferung der ganzen Kompilation-Auflistung ein.
Nachdem, wie das Projekt zusammengebaut wurde, bekommen wir für jede Anfangs-cpp-Datei eine Dateiliste mit der Erweiterung .cod. Uns interessiert ExpertSample. cod, und nur besonderes ihr Code, der für die Funktion GetStringValue erhalten wurde. Hier ist er:
?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 ; GetStringValueDie Zahlen 10001051:0028 in der zweiten Zeile der Aufruf-Liste geben die Adresse in der GetStringValue Funktion, in der nach der Ausführung der Funktion die Kontrolle übergeben wird. Es wird die Funktion ausgeführt, die eine Zeile höher in der Aufruf-Liste angegeben wurde. Im Objektcode beginnt die Adresse der Funktion GetStringValue mit 00051 (Geben Sie Acht darauf, dass Adressen in hexadezimaler Schreibweise dargestellt werden). Lassen Sie uns zu diesem Wert 0028 hinzufügen und wir bekommen diese Adresse 00079. Unter dieser Adresse ist die Anweisung add esp,12, die direkt nach der Anweisung der Funktion memcpy steht. Wir haben diesen Ort gefunden.
Nun versuchen wir die Situation zu untersuchen, wenn ein Fehler direkt in der importierten Funktion auftritt. Lassen Sie uns den Code modifizieren:
__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); }
Wir haben den Aufruf der Funktion memcpy mit unserer eigenen byteweisen Datenkopierungsloop ersetzt. Aber wir haben keine Überprüfung um 0 erstellt, damit wir die Ausnahmesituation und den Fehlerbericht erhalten könnten. Im neuen Bericht sieht die Aufruf-Liste ein wenig anders aus:
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]Der Fehler ist in der Adresse 003A der Funktion GetStringValue aufgetreten. Schauen wir auf die erzeugte Auflistung.
?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 ; GetStringValueDie Anfangsadresse ist die gleiche, 00051. Lassen Sie uns 003A hinzufügen und wir bekommen diese Adresse 0008B. Unter dieser Adresse ist die Anweisung mov cl, BYTE PTR [edx]. Schauen wir auf den Inhalt des Ragisters im Bericht:
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=0000Na klar, enthält EDX-Register Nullen. Wir haben den fremden Speicherbereich aufgerufen und haben den Crash bekommen.
Zum Schluss zwei Zeilen darüber, wie wir den Nullzeiger auf die importierte Funktion übergeben haben.
string null_string; string sret=GetStringValue(null_string);Als Parameter haben wir nicht initialisierte Zeile übergeben. Seien Sie vorsichtig mit den nicht initialisierten Zeilen, Überprüfen Sie immer die empfangenen Nullzeiger, und mögen bei Ihnen weniger Crashs werden.
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/1414
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.