Partilhar via


Depuração de viagem no tempo - Exemplo de passo a passo do aplicativo

Logótipo de depuração de viagens no tempo com um relógio.

Este laboratório introduz a Depuração de Viagem no Tempo (TTD), usando um pequeno programa de exemplo com uma falha de código. TTD é usado para depurar, identificar e determinar a causa raiz do problema. Embora o problema neste pequeno programa seja fácil de encontrar, o procedimento geral pode ser usado em códigos mais complexos. Este procedimento geral pode ser resumido da seguinte forma.

  1. Capture um traço de viagem no tempo do programa falhado.
  2. Use o comando dx (Display Debugger Object Model Expression) para localizar o evento de exceção armazenado na gravação.
  3. Use o comando !tt (viagem no tempo) para viajar até a posição do evento de exceção no rastreamento.
  4. A partir desse ponto no rastreamento, um único passo para trás até que o código de falha em questão entre em escopo.
  5. Com o código de falha no escopo, observe os valores locais e desenvolva uma hipótese de uma variável que pode conter um valor incorreto.
  6. Determine o endereço de memória da variável com o valor incorreto.
  7. Defina um ponto de interrupção de acesso à memória (ba) no endereço da variável suspeita usando o comando ba (Break on Access).
  8. Use g- para voltar ao último ponto de acesso à memória da variável suspeita.
  9. Veja se esse local, ou algumas instruções antes, é o ponto da falha do código. Se sim, está feito. Se o valor incorreto veio de alguma outra variável, defina outra quebra no ponto de interrupção de acesso na segunda variável.
  10. Use g- para voltar ao último ponto de acesso à memória na segunda variável suspeita. Veja se esse local ou algumas instruções antes contém a falha de código. Se for o caso, já está.
  11. Repita esse processo retrocedendo até que o código que definiu o valor incorreto que causou o erro seja localizado.

Embora as técnicas gerais descritas neste procedimento se apliquem a um amplo conjunto de problemas de código, há problemas de código exclusivos que exigirão uma abordagem exclusiva. As técnicas ilustradas no guia passo a passo deverão servir para expandir o seu conjunto de ferramentas de depuração e ilustrarão algumas das possibilidades com um rastro TTD.

Objetivos do laboratório

Depois de concluires este laboratório, serás capaz de usar o procedimento geral com um traço de viagem no tempo para localizar problemas no código.

Configuração do laboratório

Você precisará do seguinte hardware para poder concluir o laboratório.

  • Um laptop ou computador desktop (host) executando o Windows 10 ou Windows 11

Você precisará do seguinte software para poder concluir o laboratório.

  • O WinDbg. Para obter informações sobre como instalar o WinDbg, consulte WinDbg - Installation
  • Visual Studio para criar o código C++ de exemplo.

O laboratório tem as seguintes três seções.

Seção 1: Criar o código de exemplo

Na Seção 1, você criará o código de exemplo usando o Visual Studio.

Criar o aplicativo de exemplo no Visual Studio

  1. No Microsoft Visual Studio, clique em Arquivo>Novo>Projeto/Solução... e clique nos modelos do Visual C++.

    Selecione o aplicativo de console Win32.

    Indique o nome do projeto como DisplayGreeting e clique em OK.

  2. Desmarque as verificações do Ciclo de Vida de Desenvolvimento de Segurança (SDL).

    Configurações do Assistente de Aplicativo Win32 no Visual Studio.

  3. Clique em Concluir.

  4. Cole o texto a seguir no painel DisplayGreeting.cpp no Visual Studio.

    // DisplayGreeting.cpp : Defines the entry point for the console application.
    //
    
    #include "stdafx.h"
    #include <array>
    #include <stdio.h>
    #include <string.h>
    
    void GetCppConGreeting(wchar_t* buffer, size_t size)
    {
        wchar_t const* const message = L"HELLO FROM THE WINDBG TEAM. GOOD LUCK IN ALL OF YOUR TIME TRAVEL DEBUGGING!";
    
        wcscpy_s(buffer, size, message);
    }
    
    int main()
    {
         std::array <wchar_t, 50> greeting{};
         GetCppConGreeting(greeting.data(), sizeof(greeting));
    
         wprintf(L"%ls\n", greeting.data());
    
         return 0;
    }
    
  5. No Visual Studio, clique em Project>propriedades DisplayGreeting. Em seguida, clique em C/C++ e Code Generation.

    Defina as seguintes propriedades.

    Configurações Valor
    Verificação de segurança Desativar verificação de segurança (/GS-)
    Verificações básicas de tempo de execução Predefinido

    Observação

    Embora essas configurações não sejam recomendadas, é possível imaginar um cenário em que alguém aconselharia o uso dessas configurações para agilizar a codificação ou facilitar determinados ambientes de teste.

  6. No Visual Studio, clique em Build>Build Solution.

    Se tudo correr bem, as janelas de compilação devem exibir uma mensagem indicando que a compilação foi bem-sucedida.

  7. Localize os arquivos de aplicativo de exemplo criados

    No Gerenciador de Soluções, clique com o botão direito do mouse no projeto DisplayGreeting e selecione Abrir Pasta no Explorador de Arquivos.

    Navegue até à pasta Debug que contém o ficheiro exe compilado e o ficheiro de símbolos pdb para o exemplo. Por exemplo, você navegaria até C:\Projects\DisplayGreeting\Debug, se essa for a pasta na qual seus projetos estão armazenados.

  8. Execute o aplicativo de exemplo com a falha de código

    Clique duas vezes no arquivo exe para executar o aplicativo de exemplo.

    Captura de ecrã da consola que executa o ficheiro DisplayGreeting.exe.

    Se essa caixa de diálogo for exibida, selecione Fechar programa

    Captura de ecrã da caixa de diálogo que apresenta 'DisplayGreeting.exe deixou de funcionar'

    Na próxima seção do passo a passo, registraremos a execução do aplicativo de exemplo para ver se podemos determinar por que essa exceção está ocorrendo.

Seção 2: Gravar um rastreamento do exemplo "DisplayGreeting"

Na Seção 2, você gravará um traço do exemplo de comportamento incorreto do aplicativo "DisplayGreeting"

Para iniciar o aplicativo de exemplo e registrar um rastreamento TTD, siga estas etapas. Para obter informações gerais sobre como gravar rastreamentos TTD, consulte Depuração de viagem no tempo - Gravar um rastreamento

  1. Execute o WinDbg como administrador, para poder gravar rastreamentos de viagem no tempo.

  2. No WinDbg, selecione Arquivo>Iniciar depuração>Executável (avançado) Iniciar.

  3. Digite o caminho para o executável no modo utilizador que pretende gravar ou selecione Pesquisar para navegar até ao executável. Para obter informações sobre como trabalhar com o menu executável de inicialização no WinDbg, consulte WinDbg - Iniciar uma sessão de modo de usuário.

    Captura de tela do WinDbg com a caixa de seleção 'Gravar com Depuração com Reversão Temporal' no ecrã Executar Programa (Avançado).

  4. Marque a caixa Registro com Depuração de Viagem no Tempo para gravar um rastreamento quando o executável for lançado.

  5. Clique Configurar e gravar para iniciar a gravação.

  6. Quando a caixa de diálogo "Configurar gravação" aparecer, clique em gravar para iniciar o executável e iniciar a gravação.

    Captura de tela do WinDbg exibindo a caixa de diálogo Configurar gravação com o caminho definido como c: temp.

  7. A caixa de diálogo de gravação é exibida indicando que o traço está a ser gravado. Pouco tempo depois, o aplicativo trava.

  8. Clique em Fechar programa para descartar a caixa de diálogo "DisplayGreeting parou de funcionar".

    caixa de diálogo mostrando que o aplicativo DisplayGreeting parou de funcionar.

  9. Quando o programa falha, o arquivo de rastreamento será fechado e gravado no disco.

    Captura de ecrã da saída do WinDbg exibindo 1/1 quadros-chave indexados.

  10. O depurador abrirá automaticamente o arquivo de rastreamento e o indexará. A indexação é um processo que permite a depuração eficiente do arquivo de rastreamento. Esse processo de indexação levará mais tempo para arquivos de rastreamento maiores.

    (5120.2540): Break instruction exception - code 80000003 (first/second chance not available)
    Time Travel Position: D:0 [Unindexed] Index
    !index
    Indexed 10/22 keyframes
    Indexed 20/22 keyframes
    Indexed 22/22 keyframes
    Successfully created the index in 755ms.
    

Observação

Um quadro-chave é um local em um traço usado para indexação. Os quadros-chave são gerados automaticamente. Rastreamentos maiores conterão mais quadros-chave.

  1. Neste ponto, você está no início do arquivo de rastreamento e está pronto para avançar e retroceder no tempo.

    Agora que você gravou um rastreamento TTD, pode reproduzir o rastreamento ou trabalhar com o arquivo de rastreamento, por exemplo, compartilhá-lo com um colega de trabalho. Para obter mais informações sobre como trabalhar com arquivos de rastreamento, consulte Depuração de viagem no tempo - Trabalhando com arquivos de rastreamento

Na próxima seção deste laboratório, analisaremos o arquivo de rastreamento para localizar o problema com nosso código.

Seção 3: Analisar a gravação do arquivo de rastreamento para identificar o problema de código

Na Seção 3, você analisará a gravação do arquivo de rastreamento para identificar o problema de código.

Configurar o ambiente WinDbg

  1. Adicione a localização do símbolo local ao caminho do símbolo e recarregue os símbolos, digitando os seguintes comandos.

    .sympath+ C:\MyProjects\DisplayGreeting\Debug
    .reload
    
  2. Adicione o seu código local ao caminho de origem inserindo o seguinte comando.

    .srcpath+ C:\MyProjects\DisplayGreeting\DisplayGreeting
    
  3. Para visualizar o estado da pilha e das variáveis locais, na faixa de opções do WinDbg, selecione Visualizar e Locais e Visualizar e Pilha. Organize as janelas para permitir que você as visualize, o código-fonte e as janelas de comando ao mesmo tempo.

  4. Na faixa de opções do WinDbg, selecione de origem e Arquivo de código-fonte aberto . Localize o arquivo DisplayGreeting.cpp e abra-o.

Examinar a exceção

  1. Quando o arquivo de rastreamento foi carregado, ele exibe informações de que ocorreu uma exceção.

    2fa8.1fdc): Break instruction exception - code 80000003 (first/second chance not available)
    Time Travel Position: 15:0
    eax=68ef8100 ebx=00000000 ecx=77a266ac edx=69614afc esi=6961137c edi=004da000
    eip=77a266ac esp=0023f9b4 ebp=0023fc04 iopl=0         nv up ei pl nz na pe nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
    ntdll!LdrpInitializeProcess+0x1d1c:
    77a266ac 83bdbcfeffff00  cmp     dword ptr [ebp-144h],0 ss:002b:0023fac0=00000000
    
  2. Use o comando dx para listar todos os eventos na gravação. O evento de exceção é listado nos eventos.

    0:000> dx -r1 @$curprocess.TTD.Events
    ...
    [0x2c]           : Module Loaded at position: 9967:0
    [0x2d]           : Exception at 9BDC:0
    [0x2e]           : Thread terminated at 9C43:0
    ...
    
    

    Observação

    Neste passo a passo, três períodos são usados para indicar que a saída estranha foi removida.

  3. Clique no evento Exception para exibir informações sobre esse evento TTD.

    0:000> dx -r1 @$curprocess.TTD.Events[17]
    @$curprocess.TTD.Events[17]                 : Exception at 68:0
        Type             : Exception
        Position         : 68:0 [Time Travel]
        Exception        : Exception of type Hardware at PC: 0X540020
    
  4. Clique no campo Exceção para detalhar melhor os dados da exceção.

    0:000> dx -r1 @$curprocess.TTD.Events[17].Exception
    @$curprocess.TTD.Events[17].Exception                 : Exception of type Hardware at PC: 0X540020
        Position         : 68:0 [Time Travel]
        Type             : Hardware
        ProgramCounter   : 0x540020
        Code             : 0xc0000005
        Flags            : 0x0
        RecordAddress    : 0x0
    

    Os dados de exceção indicam que esta é uma falha de hardware lançada pela CPU. Ele também fornece o código de exceção de 0xc0000005 que indica que esta é uma violação de acesso. Isso normalmente indica que estávamos tentando gravar na memória à qual não temos acesso.

  5. Clique no link [Viagem no tempo] no evento de exceção para passar para essa posição no rastreamento.

    0:000> dx @$curprocess.TTD.Events[17].Exception.Position.SeekTo()
    Setting position: 68:0
    
    @$curprocess.TTD.Events[17].Exception.Position.SeekTo()
    (16c8.1f28): Break instruction exception - code 80000003 (first/second chance not available)
    Time Travel Position: 68:0
    eax=00000000 ebx=00cf8000 ecx=99da9203 edx=69cf1a6c esi=00191046 edi=00191046
    eip=00540020 esp=00effe4c ebp=00520055 iopl=0         nv up ei pl zr na pe nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
    00540020 ??
    

    É importante notar nesta saída que a pilha e o ponteiro base estão a apontar para endereços muito diferentes.

    esp=00effe4c ebp=00520055
    

    Isso pode indicar corrupção da pilha, possivelmente uma função retornou e, em seguida, corrompeu a pilha. Para validar isso, precisamos voltar atrás no tempo para antes de o estado da CPU ter sido corrompido e ver se conseguimos determinar quando a corrupção da pilha ocorreu.

Examine as variáveis locais e defina um ponto de interrupção de código

No ponto de falha no rastreamento, é comum acabar alguns passos após a verdadeira causa no código de manipulação de erros. Com a viagem no tempo, podemos voltar uma instrução de cada vez, para localizar a verdadeira causa raiz.

  1. Na faixa de opções do Home, use o comando Step Into Back para retroceder três instruções. Ao fazer isso, continue a examinar as janelas da pilha e da memória.

    A janela de comando exibirá a posição da viagem no tempo e os registros à medida que você recua três instruções.

    0:000> t-
    Time Travel Position: 67:40
    eax=00000000 ebx=00cf8000 ecx=99da9203 edx=69cf1a6c esi=00191046 edi=00191046
    eip=00540020 esp=00effe4c ebp=00520055 iopl=0         nv up ei pl zr na pe nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
    00540020 ??              ???
    
    0:000> t-
    Time Travel Position: 67:3F
    eax=00000000 ebx=00cf8000 ecx=99da9203 edx=69cf1a6c esi=00191046 edi=00191046
    eip=0019193d esp=00effe48 ebp=00520055 iopl=0         nv up ei pl zr na pe nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
    DisplayGreeting!main+0x4d:
    0019193d c3
    
    0:000> t-
    Time Travel Position: 67:39
    eax=0000004c ebx=00cf8000 ecx=99da9203 edx=69cf1a6c esi=00191046 edi=00191046
    eip=00191935 esp=00effd94 ebp=00effe44 iopl=0         nv up ei pl nz ac po nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000212
    DisplayGreeting!main+0x45:
    

    Observação

    Neste passo a passo, a saída do comando mostra os comandos que podem ser usados em vez das opções do Menu da interface do usuário para permitir que os usuários com uma preferência de uso da linha de comando usem comandos de linha de comando.

  2. Neste ponto do rastreamento, nossa pilha e ponteiro base têm valores que fazem mais sentido, então parece que estamos nos aproximando do ponto no código onde a corrupção ocorreu.

    esp=00effd94 ebp=00effe44
    

    Também é interessante que a janela locais contém valores do nosso aplicativo de destino e a janela do código-fonte está destacando a linha de código que está pronta para ser executada neste ponto do rastreamento.

  3. Para investigar melhor, podemos abrir uma janela de memória para visualizar o conteúdo perto do endereço de memória do ponteiro base de 0x00effe44.

  4. Para exibir os caracteres ASCII associados, na faixa de opções Memória, selecione Texto e depois ASCII.

    Captura de tela do WinDbg Preview exibindo memória, saída ASCII e janela de código-fonte.

  5. Em vez de o ponteiro base apontar para uma instrução, ele está apontando para o texto da mensagem. Portanto, algo não está bem aqui, isto pode estar perto do momento em que corrompemos a pilha. Para investigar mais a fundo, vamos definir um ponto de interrupção.

Observação

Neste exemplo muito pequeno, seria muito fácil apenas olhar no código, mas se houver centenas de linhas de código e dezenas de sub-rotinas, as técnicas descritas aqui podem ser usadas para diminuir o tempo necessário para localizar o problema.

TTD e pontos de interrupção

O uso de pontos de interrupção é uma abordagem comum para pausar a execução de código em algum evento de interesse. O TTD permite que você defina um ponto de interrupção e viaje de volta no tempo até que esse ponto de interrupção seja atingido após o rastreamento ter sido registrado. A capacidade de examinar o estado do processo após a ocorrência de um problema, para determinar o melhor local para um ponto de interrupção, permite fluxos de trabalho de depuração adicionais exclusivos do TTD.

Pontos de interrupção de acesso à memória

Você pode definir pontos de interrupção que são acionados quando um local de memória é acessado. Use o comando ba (break on access), com a sintaxe a seguir.

ba <access> <size> <address> {options}
Opção Descrição
e executar (quando a CPU executa uma instrução a partir do endereço)
r leitura/gravação (quando a CPU lê ou grava no endereço)
w escrever (quando a CPU grava no endereço)

Observe que você só pode definir quatro pontos de interrupção de dados a qualquer momento e cabe a você certificar-se de que está alinhando seus dados corretamente ou não acionará o ponto de interrupção (as palavras devem terminar em endereços divisíveis por 2, dwords devem ser divisíveis por 4 e quadwords por 0 ou 8).

Defina o ponto de paragem no acesso à memória para o ponteiro base

  1. Neste ponto da análise, pretendemos definir um ponto de interrupção no acesso à memória de escrita para o ponteiro base, ebp, que no nosso exemplo é 00effe44. Para fazer isso, use o comando ba usando o endereço que queremos monitorar. Queremos monitorizar gravações para quatro bytes, por isso especificamos w4.

    0:000> ba w4 00effe44
    
  2. Selecione Exibir e, em seguida, Pontos de interrupção para confirmar que eles estão definidos como pretendido.

    Captura de ecrã da janela WinDbg Breakpoints exibindo um único ponto de interrupção.

  3. No menu Início, selecione Voltar para viajar no tempo até que o ponto de interrupção seja atingido.

    0:000> g-
    Breakpoint 0 hit
    Time Travel Position: 5B:92
    eax=0000000f ebx=003db000 ecx=00000000 edx=00cc1a6c esi=00d41046 edi=0053fde8
    eip=00d4174a esp=0053fcf8 ebp=0053fde8 iopl=0         nv up ei pl nz ac pe nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000216
    DisplayGreeting!DisplayGreeting+0x3a:
    00d4174a c745e000000000  mov     dword ptr [ebp-20h],0 ss:002b:0053fdc8=cccccccc
    
  4. Selecione Exibir e, em seguida, Locais. Na janela locais, podemos ver que a variável de destino tem apenas parte da mensagem, enquanto a de origem contém todo o texto. Essas informações suportam a ideia de que a pilha foi corrompida.

    Captura de tela do WinDbg exibindo a janela Locals.

  5. Neste ponto, podemos examinar a pilha de programas para ver qual código está ativo. Na faixa de opções Vista, selecione Empilhar.

    Captura de tela do WinDbg exibindo a janela Stack.

Como é muito improvável que a função wscpy_s() fornecida pela Microsoft tenha um bug de código como este, olhamos mais longe na pilha. A pilha mostra que Greeting!main chama Greeting!GetCppConGreeting. Em nosso exemplo de código muito pequeno, poderíamos simplesmente abrir o código neste ponto e provavelmente encontrar o erro com bastante facilidade. Mas para ilustrar as técnicas que podem ser usadas com programas maiores e mais complexos, vamos definir um novo ponto de interrupção para investigar mais.

Defina a quebra no ponto de interrupção de acesso para a função GetCppConGreeting

  1. Use a janela de pontos de interrupção para limpar o ponto de interrupção existente clicando com o botão direito do mouse no ponto de interrupção existente e selecionando Remover.

  2. Determine o endereço do DisplayGreeting! GetCppConGreeting função usando o comando dx.

    0:000> dx &DisplayGreeting!GetCppConGreeting
    &DisplayGreeting!GetCppConGreeting                 : 0xb61720 [Type: void (__cdecl*)(wchar_t *,unsigned int)]
        [Type: void __cdecl(wchar_t *,unsigned int)]
    
  3. Use o comando ba para definir um ponto de interrupção no acesso à memória. Como a função será apenas lida da memória para execução, precisamos definir um ponto de interrupção r - read.

    0:000> ba r4 b61720
    
  4. Confirme se um ponto de interrupção de leitura de hardware está ativo na janela de pontos de interrupção.

    Captura de ecrã da janela WinDbg Breakpoints exibindo um único ponto de interrupção para leitura de hardware.

  5. Como estamos nos perguntando sobre o tamanho da string de saudação, vamos definir uma janela de monitorização para exibir o valor de sizeof(greeting). Na faixa de opções Exibir, selecione Watch e insira sizeof(saudação). Se o valor não estiver no escopo, a janela de observação será exibida - Não é possível vincular o nome 'saudação'.

    Captura de tela do WinDbg exibindo uma janela Watch Locals.

  6. No menu Viagem no Tempo, use Viagem no Tempo para iniciar ou use o comando !tt 0para mover-se para o início do rastreio.

    0:000> !tt 0
    Setting position to the beginning of the trace
    Setting position: 15:0
    (1e5c.710): Break instruction exception - code 80000003 (first/second chance not available)
    Time Travel Position: 15:0
    eax=68e28100 ebx=00000000 ecx=77a266ac edx=69e34afc esi=69e3137c edi=00fa2000
    eip=77a266ac esp=00ddf3b8 ebp=00ddf608 iopl=0         nv up ei pl nz na pe nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
    ntdll!LdrpInitializeProcess+0x1d1c:
    77a266ac 83bdbcfeffff00  cmp     dword ptr [ebp-144h],0 ss:002b:00ddf4c4=00000000
    
  7. No menu Página Inicial, selecione Ir ou use o comando g para avançar no código até que o ponto de interrupção seja atingido.

    0:000> g
    Breakpoint 2 hit
    Time Travel Position: 4B:1AD
    eax=00ddf800 ebx=00fa2000 ecx=00ddf800 edx=00b61046 esi=00b61046 edi=00b61046
    eip=00b61721 esp=00ddf7a4 ebp=00ddf864 iopl=0         nv up ei pl nz na po nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
    DisplayGreeting!GetCppConGreeting+0x1:
    00b61721 8bec            mov     ebp,esp
    
  8. No menu Página Inicial, selecione *Step Out Back* ou utilize o comando g-u para voltar um passo.

    0:000> g-u
    Time Travel Position: 4B:1AA
    eax=00ddf800 ebx=00fa2000 ecx=00ddf800 edx=00b61046 esi=00b61046 edi=00b61046
    eip=00b61917 esp=00ddf7ac ebp=00ddf864 iopl=0         nv up ei pl nz na po nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
    DisplayGreeting!main+0x27:
    00b61917 e8def7ffff      call    DisplayGreeting!ILT+245(?GetCppConGreetingYAXPA_WIZ) (00b610fa)
    
  9. Parece que encontramos a causa raiz. A array de saudação que declaramos tem 50 caracteres de comprimento, enquanto o sizeof(saudação) que passamos para GetCppConGreeting é 0x64, 100.

    Captura de tela do WinDbg exibindo o código DisplayGreeting com uma janela Watch Locals mostrando 0x64.

    À medida que analisamos a questão do tamanho, também notamos que a mensagem tem 75 caracteres de comprimento e 76 quando se inclui o final do caractere de cadeia de caracteres.

    HELLO FROM THE WINDBG TEAM. GOOD LUCK IN ALL OF YOUR TIME TRAVEL DEBUGGING!
    
  10. Uma maneira de corrigir o código seria expandir o tamanho da matriz de caracteres para 100.

    std::array <wchar_t, 100> greeting{};
    

    E também precisamos mudar sizeof(greeting) para size(greeting) nesta linha de código.

     GetCppConGreeting(greeting.data(), size(greeting));
    
  11. Para validar essas correções, podemos recompilar o código e confirmar que ele é executado sem erros.

Definindo um ponto de interrupção usando a janela de origem

  1. Uma maneira alternativa de realizar essa investigação seria definir um ponto de interrupção clicando em qualquer linha de código. Por exemplo, clicar no lado direito da linha de definição std:array na janela de origem definirá um ponto de interrupção lá.

    Captura de ecrã da janela de origem no WinDbg com um ponto de interrupção estabelecido em std::array.

  2. No menu Viagem no Tempo, use Time Travel para iniciar o comando para mover para o início da trilha.

    0:000> !tt 0
    Setting position to the beginning of the trace
    Setting position: 15:0
    (1e5c.710): Break instruction exception - code 80000003 (first/second chance not available)
    Time Travel Position: 15:0
    eax=68e28100 ebx=00000000 ecx=77a266ac edx=69e34afc esi=69e3137c edi=00fa2000
    eip=77a266ac esp=00ddf3b8 ebp=00ddf608 iopl=0         nv up ei pl nz na pe nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
    ntdll!LdrpInitializeProcess+0x1d1c:
    77a266ac 83bdbcfeffff00  cmp     dword ptr [ebp-144h],0 ss:002b:00ddf4c4=00000000
    
  3. Na Faixa de Opções Principal, clique em Ir para retroceder até que o breakpoint seja alcançado.

    Breakpoint 0 hit
    Time Travel Position: 5B:AF
    eax=0000000f ebx=00c20000 ecx=00000000 edx=00000000 esi=013a1046 edi=00effa60
    eip=013a17c1 esp=00eff970 ebp=00effa60 iopl=0         nv up ei pl nz na po nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
    DisplayGreeting!DisplayGreeting+0x41:
    013a17c1 8bf4            mov     esi,esp
    

Configurar o ponto de break no acesso para a variável de saudação

Outra maneira alternativa de realizar essa investigação seria definir um ponto de interrupção em variáveis suspeitas e examinar qual código está alterando-as. Por exemplo, para definir um ponto de interrupção na variável de saudação no método GetCppConGreeting, use este procedimento.

Esta parte do passo a passo pressupõe que você ainda esteja localizado no ponto de interrupção da seção anterior.

  1. A partir de Ver e, em seguida, Locais. Na janela de locais, saudação está disponível no contexto atual, para que possamos determinar o seu local de memória.

  2. Utilize o comando dx para examinar a matriz de saudações .

    0:000> dx &greeting
    &greeting                 : ddf800 : { size=50 } [Type: std::array<wchar_t,50> *]
       [<Raw View>]     [Type: std::array<wchar_t,50>]
       [0]              : 3 [Type: wchar_t]
       [1]              : 0 [Type: wchar_t]
    

    Neste rastreamento, saudação está localizado na memória em ddf800.

  3. Use a janela de pontos de interrupção para limpar qualquer ponto de interrupção existente clicando com o botão direito do mouse no ponto de interrupção existente e selecionando Remover.

  4. Defina o ponto de interrupção com o comando ba usando o endereço de memória que queremos monitorar para acesso de gravação.

    ba w4 ddf800
    
  5. No menu Viagem no Tempo, use Viagem no tempo para iniciar comando para mover para o início do rastreamento.

    0:000> !tt 0
    Setting position to the beginning of the trace
    Setting position: 15:0
    (1e5c.710): Break instruction exception - code 80000003 (first/second chance not available)
    Time Travel Position: 15:0
    eax=68e28100 ebx=00000000 ecx=77a266ac edx=69e34afc esi=69e3137c edi=00fa2000
    eip=77a266ac esp=00ddf3b8 ebp=00ddf608 iopl=0         nv up ei pl nz na pe nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
    ntdll!LdrpInitializeProcess+0x1d1c:
    77a266ac 83bdbcfeffff00  cmp     dword ptr [ebp-144h],0 ss:002b:00ddf4c4=00000000
    
  6. No menu Início, selecione Ir para deslocar-se até o primeiro ponto de acesso à memória do array de saudação.

    0:000> g-
    Breakpoint 0 hit
    Time Travel Position: 5B:9C
    eax=cccccccc ebx=002b1000 ecx=00000000 edx=68d51a6c esi=013a1046 edi=001bf7d8
    eip=013a1735 esp=001bf6b8 ebp=001bf7d8 iopl=0         nv up ei pl nz na po nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
    DisplayGreeting!GetCppConGreeting+0x25:
    013a1735 c745ec04000000  mov     dword ptr [ebp-14h],4 ss:002b:001bf7c4=cccccccc
    

    Como alternativa, poderíamos ter percorrido até ao final do rastreamento e analisado o código em sentido inverso para encontrar o último ponto no rastreamento onde o endereço de memória do array foi gravado.

Use o TTD. Objetos de memória para visualizar o acesso à memória

Outra maneira de determinar em que pontos no rastreamento a memória foi acessada é usar os objetos TTD.Memory e o comando dx.

  1. Use o comando dx para examinar o array greeting.

    0:000> dx &greeting
    &greeting                 : 0xddf800 [Type: std::array<wchar_t,50> *]
       [+0x000] _Elems           : "꽘棶檙瞝???" [Type: wchar_t [50]]
    

    Neste rastreamento, a saudação está localizada na memória em ddf800.

  2. Use o comando dx para examinar os quatro bytes na memória a partir desse endereço com acesso de leitura e escrita.

    0:000> dx -r1 @$cursession.TTD.Memory(0xddf800,0xddf804, "rw")
    @$cursession.TTD.Memory(0x1bf7d0,0x1bf7d4, "rw")                
        [0x0]           
        [0x1]           
        [0x2]           
        [0x3]           
        [0x4]           
        [0x5]           
        [0x6]           
        [0x7]           
        [0x8]           
        [0x9]           
        [0xa]           
        ...         
    
  3. Clique em qualquer uma das ocorrências para exibir mais informações sobre essa ocorrência de acesso à memória.

    0:000> dx -r1 @$cursession.TTD.Memory(0xddf800,0xddf804, "rw")[5]
    @$cursession.TTD.Memory(0xddf800,0xddf804, "rw")[5]                
        EventType        : MemoryAccess
        ThreadId         : 0x710
        UniqueThreadId   : 0x2
        TimeStart        : 27:3C1 [Time Travel]
        TimeEnd          : 27:3C1 [Time Travel]
        AccessType       : Write
        IP               : 0x6900432f
        Address          : 0xddf800
        Size             : 0x4
        Value            : 0xddf818
        OverwrittenValue : 0x0
        SystemTimeStart  : Monday, November 18, 2024 23:01:43.400
        SystemTimeEnd    : Monday, November 18, 2024 23:01:43.400
    
  4. Clique em [Viagem no tempo] para que o TimeStart posicione o rastreamento no momento desejado.

    0:000> dx @$cursession.TTD.Memory(0xddf800,0xddf804, "rw")[5].TimeStart.SeekTo()
    @$cursession.TTD.Memory(0xddf800,0xddf804, "rw")[5].TimeStart.SeekTo()
    (1e5c.710): Break instruction exception - code 80000003 (first/second chance not available)
    Time Travel Position: 27:3C1
    eax=00ddf81c ebx=00fa2000 ecx=00ddf818 edx=ffffffff esi=00000000 edi=00b61046
    eip=6900432f esp=00ddf804 ebp=00ddf810 iopl=0         nv up ei pl nz ac po nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000212
    ucrtbased!_register_onexit_function+0xf:
    6900432f 51              push    ecx
    
  5. Se estivermos interessados na última ocorrência de acesso à memória de leitura/gravação no rastreamento, podemos clicar no último item da lista ou adicionar a função .Last() ao final do comando dx.

    0:000> dx -r1 @$cursession.TTD.Memory(0xddf800,0xddf804, "rw").Last()
    @$cursession.TTD.Memory(0xddf800,0xddf804, "rw").Last()                
        EventType        : MemoryAccess
        ThreadId         : 0x710
        UniqueThreadId   : 0x2
        TimeStart        : 53:100E [Time Travel]
        TimeEnd          : 53:100E [Time Travel]
        AccessType       : Read
        IP               : 0x690338e4
        Address          : 0xddf802
        Size             : 0x2
        Value            : 0x45
        SystemTimeStart  : Monday, November 18, 2024 23:01:43.859
        SystemTimeEnd    : Monday, November 18, 2024 23:01:43.859
    
  6. Poderíamos então clicar em [Viagem no Tempo] para passar para essa posição no rastreamento e olhar mais para a execução do código naquele ponto, usando as técnicas descritas anteriormente neste laboratório.

Para mais informações sobre os objetos de memória TTD, consulte TTD. Objeto de memória.

Resumo

Neste exemplo muito pequeno, o problema poderia ter sido determinado olhando para as poucas linhas de código, mas em programas maiores as técnicas apresentadas aqui podem ser usadas para diminuir o tempo necessário para localizar um problema.

Depois que um rastreamento é registrado, as etapas de rastreamento e reprodução podem ser compartilhadas e o problema será reproduzível em qualquer PC.

Ver também

Depuração de viagem no tempo - Visão geral

Depuração de "Viagem no Tempo" - Gravação

Depuração de Viagem no Tempo - Reproduzir rastreamento

Depuração de Viagem no Tempo - Trabalhando com arquivos de rastreamento