English Русский 中文 Español Deutsch 日本語
Como usar registros de parada de funcionamento para depurar os seus próprios DLLs

Como usar registros de parada de funcionamento para depurar os seus próprios DLLs

MetaTrader 4Exemplos | 18 fevereiro 2016, 13:37
606 0
Slava
Slava

O terminal do cliente MetaTrader 4 possui um meio integrado para detectar condições de erro que ocorrem durante o trabalho do terminal e gerar registros de parada de funcionamento quando tais erros são reportados. O relatório gerado é armazenado no arquivo logs\crashlog.log, e é enviado ao servidor de transações durante a próxima inicialização do terminal do cliente. Deve-se notar que o relatório de condição de erro não contém nenhuma informação pessoal do usuário, mas apenas os dados do sistema que permitem a localização do erro no terminal do cliente. Estas informações são muito importantes para os fabricantes, pois são usadas para corrigir erros críticos. A partir delas, o software desenvolvido irá se tornar ainda mais resistente a paradas de funcionamento.

De 25 a 30% de todos os registros de parada de funcionamento recebidos de usuários surgem por conta de erros ocorridos quando funções importadas de dlls personalizados são executadas. Estas informações não ajudarão de modo algum os desenvolvedores do terminal do cliente, mas podem auxiliar os desenvolvedores dos dlls correspondentes a solucionar problemas. Nós mostraremos como os dados do relatório de erros podem ser usados. Exemplos chamados ExpertSample.dll e ExportFunctions. mq4, que podem ser encontrados no diretório experts\samples serviram como base.



O texto completo do relatório de erros é apresentado abaixo:

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]


Então, o que aconteceu?

  • Exceção : C0000005 significa uma condição de erro ocorrida devido a uma violação de acesso.
  • Tipo de acesso : "leitura" significa que houve mais uma tentativa de realizar uma leitura.
  • Endereço de acesso : 00000000 significa que a memória fora-de-processo possui um endereço zero.
Agora vamos examinar a pilha de chamadas:

O endereço 77C36FA3 é o mesmo que aquele no topo da pilha. Isso significa que o erro ocorreu durante a execução da função memcpy, que copia o conteúdo de uma área da memória à outra. Além disso, nós podemos saber com bastante certeza se houve uma tentativa de copiar dados da área da memória que possui um endereço zero.

A segunda linha da pilha de chamadas nos informa a respeito de qual função chamou a função memcpy com parâmetros errados. Esta é a função GetStringValue da biblioteca chamada ExpertSample.dll.

Vamos dar uma olhada no código fonte dessa função.

__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 a função memcpy é chamada apenas uma vezes dentro da função acima. Dado que o primeiro parâmetro indica a área da memória existente ocupada pela variável temp_string, podemos concluir que o segundo é o errado. De fato, não há como verificar a variável em relação a 0 no exemplo apresentado. A linha if(spar==NULL) nos protegeria de paradas de funcionamento.

Então, o que deve ser feito caso tenha havido mais de uma ligação da função memcpy na função analisada. Nas configurações do nosso projeto, vamos configurar a saída da listagem mais detalhada da compilação.



Após o projeto ter sido reconstruído, nós vamos ter um arquivo de listagem com a extensão .cod para cada arquivo .cpp. Agora nós estamos interessados no ExpertSample. cod, mas apenas na parte do código obtida para a função GetStringValue. Aqui 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
Os dígitos 10001051:0028 na segunda linha da pilha de chamadas fornecem o endereço dentro da função GetStringValue. Após a função localizada uma linha acima na pilha de chamadas ser executada, o controle será passado a este endereço. No código do objeto, a função GetStringValue começa com o endereço 00051 (deve-se notar que endereços são apresentados em notação hexadecimal). Vamos adicionar 0028 a este valor, de modo a obter o endereço 00079. Neste endereço, está situada a instrução add esp,12, imediatamente após a instrução de chamada da função memcpy. Nós encontramos este local.

Vamos investigar o caso no qual o erro ocorre imediatamente dentro da função importada. Vamos modificar o 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);
  }

Nós substituímos a chamada da função memcpy com o nosso próprio ciclo de cópia de dados em termos de bytes. Mas nós não usamos a verificação para 0 de modo a criar uma condição de erro e o relatório de erro. No novo relatório, a pilha de chamadas será um pouco 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]
O erro ocorreu no endereço 003A na função GetStringValue. Vamos examinar a listagem gerada.
?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
O endereço inicial é o mesmo: 00051. Vamos adicionar 003A e obter o endereço 0008B. Neste endereço, está situada a instrução mov cl, BYTE PTR [edx]. Vamos examinar o conteúdo dos registros no relatório:
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
Bem, obviamente o registro EDX contém zeros. Nós acessamos a memória fora-de-processo e tivemos a parada de funcionamento.

Finalmente, duas linhas sobre como nós passamos a indicação zero para a função importada.
   string null_string;
   string sret=GetStringValue(null_string);

Nós passamos uma string não inicializada como um parâmetro. Seja cuidadoso com strings não inicializadas, sempre verifique as indicações recebidas para NULO. Esperamos que você tenha o mínimo possível de paradas de funcionamento.

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/1414

Segredos do terminal do cliente MetaTrader 4 Segredos do terminal do cliente MetaTrader 4
21 dicas para facilitar a sua vida: Recursos escondidos do terminal do cliente MetaTrader 4 Tela cheia; teclas de atalho; barra de navegação rápida; minimização de janelas; favoritos; redução do tráfego; desabilitação de notícias; conjuntos de símbolos; Market Watch (observação do mercado); modelos para testes e gráficos independentes; perfis; mira; régua eletrônica; paginação do gráfico em função de barras; histórico da conta no gráfico; tipos de pedidos pendentes; modificação de StopLoss e TakeProfit; desfazer deleção; impressão de gráfico.
Meu primeiro "graal" Meu primeiro "graal"
Aqui são examinados os erros mais comuns que levam programadores de primeira viagem a criar sistemas de transações "super lucrativos" (durante testes). São apresentados experts exemplificadores que apresentam resultados fantásticos no verificador, mas resultam em perdas em transações reais.
Segredos do terminal do cliente MetaTrader 4: Sistema de alerta Segredos do terminal do cliente MetaTrader 4: Sistema de alerta
Como estar ciente do que acontece no terminal e na sua conta sem estar permanente olhando para o monitor. Eventos do sistema; eventos personalizados; onda e arquivos executáveis​​; mensagens eletrônicas; configuração do acesso ao servidor SMTP; publicações; configuração do acesso ao servidor FTP.
Múltiplas recontagens de barra nula em alguns indicadores Múltiplas recontagens de barra nula em alguns indicadores
O artigo trata do problema de se recontar o valor do indicador no terminal do cliente MetaTrader 4 quando a barra nula muda. Nele, é delineada a ideia geral de como adicionar ao código do indicador alguns programas extras que permitem restaurar o código do programa salvo antes de recontagens múltiplas.