
Cómo utilizar los Crash logs para depurar tus propias DLLs
Entre el 25 y 30% de los crash logs que reciben los usuarios, aparecen por errores durante la ejecución de las funciones importadas de las dlls de los clientes. Esta información no ayudará de ninguna manera a los desarrolladores de la Terminal de Cliente, pero puede ayudar a los desarrolladores de las dll correspondientes en su resolución de problemas. Mostraremos cómo se puede utilizar los datos del informe de error. Se han usado como bases los ejemplos ExpertSample.dll y ExportFunctions.mq4 que pueden encontrarse en el directorio experts\samples.

El texto del informe del error al completo está a continuación:
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]
¿Qué ha pasado?
- Excepción: C0000005 significa que ha ocurrido un error por Violación de Acceso.
- Tipo de Acceso: "leído" significa que se ha intentado leer.
- Acceso Addr: 00000000 significa que la memoria fuera-de-proceso no tiene ninguna dirección.
La dirección 77C36FA3 es la misma que la que está arriba de la pila. Esto quiere decir que el error ha ocurrido durante la ejecución de la función de memoria que copia el contenido de un área de memoria a otra. Con esto podemos determinar con bastante certeza si ha habido o no un intento de copiar los datos del área de la memoria sin tener ninguna dirección.
La segunda línea de la pila de llamadas nos informa de la función memcpy con parámetros erróneos. Esta es la función GetStringValue de la librería ExpertSample.dll
Vamos a echar un vistazo al código fuente de esta función:
__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); }
Podemos ver que sólo se llama una vez a la función memcpy dentro de la función anterior. Ya que el primer parámetro indica que hay un área de memoria ocupada por la variable temp_string, podemos concluir que el parámetro erróneo es el segundo. De hecho, no hay ninguna comprobación de la variable para 0 en este ejemplo. Line if(spar==NULL) nos protegería de la caída.
Entonces, ¿qué se debería hacer si hubiera más de una llamada de la función memcpy en la función analizada? En los ajustes de nuestro proyecto, configuraremos la salida del lista más detallada de la recopilación.

Una vez que se haya reconstruido el proyecto, tendremos un archivo con la extensión .cod para cada fuente de archivo .cpp. Ahora nos interesa el ExpertSample.cod, pero sólo la parte del código que se ha obtenido para la función GetStringValue. Aquí está:
?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 ; GetStringValueLos dígitos 10001051:0028 de la segunda línea de la pila de llamadas, indica la dirección dentro de la función GetStringValue. Una vez que la función de la primera línea de la pila de llamadas se ejecute, se le dará el control a esta dirección. En el código del objeto, la función GetStringValue empieza con la dirección 00051 (debería tenerse en cuenta que estas direcciones se presentan en anotación hexadecimal). Añadimos 0028 a este valor y obtendremos la dirección 00079. En esta dirección, la instrucción add esp,12 se sitúa inmediatamente después de la instrucción de llamada de la función memcpy. Hemos encontrado este sitio.
Vamos a investigar el caso cuando tenga lugar el error dentro de la función importada. Vamos a modificar el código:
__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); }
Hemos reemplazado la función de llamada memcpy con nuestro propios datos según los bytes del bucle de copia de datos. Pero no utilizamos la comprobación para 0 para poder crear una condición de error y el informe del error. En nuevo informe, la pila de llamadas es un poco diferente:
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]El error tuvo lugar en la dirección 003A en la función GetStringValue. Vamos a mirar al listado que se ha generado.
?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 ; GetStringValueLa dirección inicial es la misma: 00051. Añadimos 003A y obtenemos la dirección 0008B. En esta dirección, se situa la instrucción mov cl,BYTE PTR [edx]. Vamos a registrar estos contenidos en el informe:
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=0000Por supuesto, EDX tiene ceros. Hemos accedido a la memoria fuera-de-proceso y hemos llegado al crash.
Al final, hay dos líneas sobre cómo hemos pasado de la indicación cero a la función importada.
string null_string; string sret=GetStringValue(null_string);Pasamos una serie no inicializada como parámetro. Tenga cuidado con las series no inicializadas, compruebe siempre los indicadores recibidos para NULL, e intente tener el menor número posible de caídas.
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/1414





- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso