Partilhar via


Depurando código otimizado e funções embutidas

Para Windows 8, o depurador e o compilador do Windows foram aprimorados para que você possa depurar o código otimizado e depurar funções embutidas. O depurador exibe parâmetros e variáveis locais, independentemente de serem armazenados em registros ou na pilha. O depurador também exibe funções embutidas na pilha de chamadas. Para funções embutidas, o depurador exibe variáveis locais, mas não parâmetros.

Quando o código é otimizado, ele é transformado para ser executado mais rapidamente e usar menos memória. Às vezes, as funções são removidas como resultado da remoção de código morto, do código sendo mesclado ou das funções sendo colocadas embutidas. Variáveis e parâmetros locais também podem ser removidos. Muitas otimizações de código removem variáveis locais que não são necessárias ou usadas; outras otimizações removem variáveis de indução em loops. A eliminação de subexibição comum mescla variáveis locais.

As compilações de varejo do Windows são otimizadas. Portanto, se você estiver executando uma compilação de varejo do Windows, é especialmente útil ter um depurador projetado para funcionar bem com código otimizado. Para tornar a depuração do código otimizado eficaz, dois recursos primários são necessários: 1) exibição precisa de variáveis locais e 2) exibição de funções embutidas na pilha de chamadas.

Exibição precisa de variáveis e parâmetros locais

Para facilitar a exibição precisa de variáveis e parâmetros locais, o compilador registra informações sobre os locais de variáveis locais e parâmetros em arquivos de símbolo (PDB). Esses registros de localização acompanham os locais de armazenamento das variáveis e os intervalos de código específicos em que esses locais são válidos. Esses registros não só ajudam a acompanhar os locais (em registros ou em slots de pilha) das variáveis, mas também o movimento das variáveis. Por exemplo, um parâmetro pode estar primeiro no registro RCX, mas é movido para um slot de pilha para liberar o RCX, depois movido para registrar R8 quando ele é fortemente usado em um loop e, em seguida, movido para um slot de pilha diferente quando o código está fora do loop. O depurador do Windows consome os registros de localização avançados nos arquivos PDB e usa o ponteiro de instrução atual para selecionar os registros de localização apropriados para as variáveis e parâmetros locais.

Esta captura de tela da janela Locais no Visual Studio mostra os parâmetros e variáveis locais de uma função em um aplicativo otimizado de 64 bits. A função não está embutida, portanto, vemos parâmetros e variáveis locais.

Captura de tela da janela Locais no Visual Studio exibindo parâmetros e variáveis locais para uma função em um aplicativo otimizado de 64 bits.

Você pode usar o comando dv -v para ver os locais dos parâmetros e variáveis locais.

Captura de tela da saída do comando exibindo os locais de parâmetros e variáveis locais usando o comando dv -v.

Observe que a janela Locais exibe os parâmetros corretamente, mesmo que eles sejam armazenados em registros.

Além de acompanhar variáveis com tipos primitivos, os registros de localização rastreiam membros de dados de estruturas e classes locais. A saída do depurador a seguir exibe estruturas locais.

0:000> dt My1
Local var Type _LocalStruct
   +0x000 i1               : 0n0 (edi)
   +0x004 i2               : 0n1 (rsp+0x94)
   +0x008 i3               : 0n2 (rsp+0x90)
   +0x00c i4               : 0n3 (rsp+0x208)
   +0x010 i5               : 0n4 (r10d)
   +0x014 i6               : 0n7 (rsp+0x200)

0:000> dt My2
Local var @ 0xefa60 Type _IntSum
   +0x000 sum1             : 0n4760 (edx)
   +0x004 sum2             : 0n30772 (ecx)
   +0x008 sum3             : 0n2 (r12d)
   +0x00c sum4             : 0n0

Aqui estão algumas observações sobre a saída anterior do depurador.

  • A estrutura local My1 ilustra que o compilador pode espalhar membros de dados de estrutura local para registros e slots de pilha não contíguos.
  • A saída do comando dt My2 será diferente da saída do comando dt _IntSum 0xefa60. Você não pode assumir que a estrutura local ocupará um bloco contíguo de memória de pilha. No caso de My2, permanece apenas sum4 no bloco de pilha original; os outros três membros de dados são movidos para registros.
  • Alguns membros de dados podem ter vários locais. Por exemplo, My2.sum2 tem dois locais: um é registrar ECX (que o depurador do Windows escolhe) e o outro é 0xefa60+0x4 (o slot de pilha original). Isso também pode acontecer para variáveis locais de tipo primitivo, e o depurador do Windows impõe heurística precedente para determinar qual local usar. Por exemplo, registrar locais sempre supera locais de pilha.

Exibição de funções embutidas na pilha de chamadas

Durante a otimização de código, algumas funções são colocadas em linha. Ou seja, o corpo da função é colocado diretamente no código, como uma expansão de macro. Não há nenhuma chamada de função e nenhum retorno ao chamador. Para facilitar a exibição de funções embutidas, o compilador armazena dados nos arquivos PDB que ajudam a decodificar as partes de código para as funções embutidas (ou seja, sequências de blocos de código em funções de chamador que pertencem às funções de receptor que estão sendo colocadas embutidas), bem como as variáveis locais (variáveis locais com escopo nesses blocos de código). Esses dados ajudam o depurador a incluir funções embutidas como parte do desenrolamento da pilha.

Suponha que você compile um aplicativo e force uma função chamada func1 a ser embutida.

__forceinline int func1(int p1, int p2, int p3)
{
   int num1 = 0;
   int num2 = 0;
   int num3 = 0;
   ...
}

Você pode usar o comando bm para definir um ponto de interrupção em func1.

0:000> bm MyApp!func1
  1: 000007f6`8d621088 @!"MyApp!func1" (MyApp!func1 inlined in MyApp!main+0x88)
0:000> g

Breakpoint 1 hit
MyApp!main+0x88:
000007f6`8d621088 488d0d21110000  lea     rcx,[MyApp!`string' (000007f6`8d6221b0)]

Depois de dar uma etapa no func1, você pode usar o comando k para ver func1 na pilha de chamadas. Você pode usar o comando dv para ver as variáveis locais para func1. Observe que a variável num3 local é mostrada como indisponível. Uma variável local pode ficar indisponível no código otimizado por vários motivos. Pode ser que a variável não exista no código otimizado. Pode ser que a variável ainda não tenha sido inicializada ou que a variável não esteja mais sendo usada.

0:000> p
MyApp!func1+0x7:
000007f6`8d62108f 8d3c33          lea     edi,[rbx+rsi]

0:000> knL
# Child-SP          RetAddr           Call Site
00 (Inline Function) --------`-------- MyApp!func1+0x7
01 00000000`0050fc90 000007f6`8d6213f3 MyApp!main+0x8f
02 00000000`0050fcf0 000007ff`c6af0f7d MyApp!__tmainCRTStartup+0x10f
03 00000000`0050fd20 000007ff`c7063d6d KERNEL32!BaseThreadInitThunk+0xd
04 00000000`0050fd50 00000000`00000000 ntdll!RtlUserThreadStart+0x1d

0:000> dv -v
00000000`0050fcb0            num1 = 0n0
00000000`0050fcb4            num2 = 0n0
<unavailable>                num3 = <value unavailable>

Se você examinar o quadro 1 no rastreamento de pilha, poderá ver as variáveis locais para a main função. Observe que duas das variáveis são armazenadas em registros.

0:000> .frame 1
01 00000000`0050fc90 000007f6`8d6213f3 MyApp!main+0x8f

0:000> dv -v
00000000`0050fd08               c = 0n7
@ebx                            b = 0n13
@esi                            a = 0n6

O depurador do Windows agrega dados de arquivos PDB para localizar todos os locais em que uma função específica foi colocada embutida. Você pode usar o comando x para listar todos os sites de chamadas de uma função embutida.

0:000> x simple!MoreCalculate
00000000`ff6e1455 simple!MoreCalculate =  (inline caller) simple!wmain+8d
00000000`ff6e1528 simple!MoreCalculate =  (inline caller) simple!wmain+160

0:000> x simple!Calculate
00000000`ff6e141b simple!Calculate =  (inline caller) simple!wmain+53

Como o depurador do Windows pode enumerar todos os sites de chamadas de uma função embutida, ele pode definir pontos de interrupção dentro da função embutida calculando os deslocamentos dos sites de chamadas. Você pode usar o comando bm (que é usado para definir pontos de interrupção que correspondem aos padrões de expressão regular) para definir pontos de interrupção para funções embutidas.

O depurador do Windows agrupa todos os pontos de interrupção definidos para uma função embutida específica em um contêiner de ponto de interrupção. Você pode manipular o contêiner de ponto de interrupção como um todo usando comandos como be, bd, bc. Confira os exemplos de comando bd 3 e bc 3 a seguir. Você também pode manipular pontos de interrupção individuais. Veja a seguir o exemplo de comando 2 .

0:000> bm simple!MoreCalculate
  2: 00000000`ff6e1455 @!"simple!MoreCalculate" (simple!MoreCalculate inlined in simple!wmain+0x8d)
  4: 00000000`ff6e1528 @!"simple!MoreCalculate" (simple!MoreCalculate inlined in simple!wmain+0x160)

0:000> bl
 0 e 00000000`ff6e13c8 [n:\win7\simple\simple.cpp @ 52]    0001 (0001)  0:**** simple!wmain
 3 e <inline function>     0001 (0001)  0:**** {simple!MoreCalculate}
     2 e 00000000`ff6e1455 [n:\win7\simple\simple.cpp @ 58]    0001 (0001)  0:**** simple!wmain+0x8d (inline function simple!MoreCalculate)
     4 e 00000000`ff6e1528 [n:\win7\simple\simple.cpp @ 72]    0001 (0001)  0:**** simple!wmain+0x160 (inline function simple!MoreCalculate)

0:000> bd 3
0:000> be 2

0:000> bl
 0 e 00000000`ff6e13c8 [n:\win7\simple\simple.cpp @ 52]    0001 (0001)  0:**** simple!wmain
 3 d <inline function>     0001 (0001)  0:**** {simple!MoreCalculate}
     2 e 00000000`ff6e1455 [n:\win7\simple\simple.cpp @ 58]    0001 (0001)  0:**** simple!wmain+0x8d (inline function simple!MoreCalculate)
     4 d 00000000`ff6e1528 [n:\win7\simple\simple.cpp @ 72]    0001 (0001)  0:**** simple!wmain+0x160 (inline function simple!MoreCalculate)

0:000> bc 3

0:000> bl
 0 e 00000000`ff6e13c8 [n:\win7\simple\simple.cpp @ 52]    0001 (0001)  0:**** simple!wmain

Como não há nenhuma chamada explícita ou instruções de retorno para funções embutidas, a etapa no nível do código-fonte é especialmente desafiadora para um depurador. Por exemplo, você pode intervir involuntariamente em uma função embutida (se a próxima instrução fizer parte de uma função embutida) ou você pode intervir e sair da mesma função embutida várias vezes (porque os blocos de código para a função embutida foram divididos e movidos pelo compilador). Para preservar a experiência de etapa familiar, o depurador do Windows mantém uma pequena pilha de chamadas conceituais para cada endereço de instrução de código e cria um computador de estado interno para executar operações de step-in, step-over e step-out. Isso fornece uma aproximação razoavelmente precisa da experiência de etapa para funções não embutidas.

Informações adicionais

Nota Você pode usar o comando .inline 0 para desabilitar a depuração de função embutida. O comando .inline 1 habilita a depuração de função embutida. Técnicas de depuração padrão

Confira também

Técnicas de depuração padrão