English Русский 中文 Deutsch 日本語 Português
Cómo utilizar los Crash logs para depurar tus propias DLLs

Cómo utilizar los Crash logs para depurar tus propias DLLs

MetaTrader 4Ejemplos | 15 febrero 2016, 07:16
3 152 0
Slava
Slava
La Terminal de Cliente de MetaTrader 4 tiene recursos integrados para detectar los errores que ocurran durante el funcionamiento de la terminal, y genera crash logs donde se presenten dichos errores. El informe se guarda en el archivo logs\crashlog.log para enviarlo al servidor de trade al siguiente inicio de la Terminal de Cliente. Hay que tener en cuenta que el informe del error no debe contener ninguna información personal del usuario, sino solamente datos del sistema que permitan localizar el error en la Terminal de Cliente. Esto es muy importante para los fabricantes, ya que se usa para corregir errores críticos. Así, el software desarrollado estará aún más a prueba de caídas.

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.
Ahora vamos a mirar la pila de llamadas.

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            ; GetStringValue
Los 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            ; GetStringValue
La 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=0000
Por 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

Secretos de la Terminal de Cliente de MetaTrader 4 Secretos de la Terminal de Cliente de MetaTrader 4
21 formas de facilitar la vida: Características actuales en la Terminal de Cliente de Meta Trader 4. Pantalla completa; atajos de teclado; barra de navegación rápida; ventanas minimizadas; favoritos; reducción de tráfico; deshabilitar noticias; configuración de símbolos; observación de mercado; plantillas para pruebas y gráficos independientes; perfiles; hilo vertical; regla electrónica; pago de gráficos según las barras; historial de recuento en el gráfico; tipos de órdenes pendientes; modificación de StopLoss y TakeProfit; deshacer eliminación; impresión de gráficos.
Mi primer "Grial" Mi primer "Grial"
Se han examinado los errores más frecuentes que llevan a los programadores primerizos a la creación de un sistema de trading "para hacerse de oro" (cuando se ha probado). Hay expertos ejemplares que muestran resultados fantásticos durante la prueba, pero se pierden resultados durante el trading real.
Secretos de la Terminal de Cliente de MetaTrader4 : Sistema de alerta Secretos de la Terminal de Cliente de MetaTrader4 : Sistema de alerta
Cómo estar al tanto de lo que pasa en la terminal y en tu cuenta sin estar permanentemente mirando la pantalla. Eventos del sistema; eventos de cliente; archivos .wav y .exe; mensajes electrónicos; configuración del acceso a servidor SMTP; publicaciones; configuración del acceso a servidor FTP.
Recuento múltiple de barras nulas en algunos indicadores Recuento múltiple de barras nulas en algunos indicadores
El artículo trata sobre el problema de recuento del valor del indicador en la Terminal de Cliente de MetaTrader 4 cuando cambia la barra nula. Resume la idea general de cómo añadir al código del indicador algún elemento de programa extra que permita restablecer el código de programa guardado antes del recuento múltiple.