Compartir a través de


Depuración de código optimizado y funciones insertadas

Para Windows 8, el depurador y el compilador de Windows se han mejorado para que pueda depurar código optimizado y depurar funciones insertadas. El depurador muestra parámetros y variables locales independientemente de si se almacenan en registros o en la pila. El depurador también muestra funciones insertadas en la pila de llamadas. Para las funciones insertadas, el depurador muestra variables locales, pero no parámetros.

Cuando el código se optimiza, se transforma para ejecutarse más rápido y usar menos memoria. A veces, las funciones se quitan como resultado de la eliminación de código fallido, el código que se combina o las funciones se colocan insertadas. También se pueden quitar variables y parámetros locales. Muchas optimizaciones de código quitan variables locales que no son necesarias o usadas; otras optimizaciones quitan variables de inducción en bucles. La eliminación común de subexpresiones combina variables locales juntas.

Las compilaciones comerciales de Windows están optimizadas. Por lo tanto, si ejecuta una compilación comercial de Windows, es especialmente útil tener un depurador diseñado para funcionar bien con código optimizado. Para que la depuración del código optimizado sea eficaz, se requieren dos características principales: 1) visualización precisa de variables locales y 2) visualización de funciones insertadas en la pila de llamadas.

Visualización precisa de variables y parámetros locales

Para facilitar la visualización precisa de variables y parámetros locales, el compilador registra información sobre las ubicaciones de variables y parámetros locales en archivos de símbolos (PDB). Estos registros de ubicación realizan un seguimiento de las ubicaciones de almacenamiento de las variables y los intervalos de código específicos en los que estas ubicaciones son válidas. Estos registros no solo ayudan a realizar un seguimiento de las ubicaciones (en registros o en ranuras de pila) de las variables, sino también el movimiento de las variables. Por ejemplo, un parámetro podría estar primero en el registro RCX, pero se mueve a una ranura de pila para liberar RCX, después se mueve para registrar R8 cuando se usa en gran medida en un bucle y, a continuación, se mueve a una ranura de pila diferente cuando el código está fuera del bucle. El depurador de Windows consume los registros de ubicación enriquecidos en los archivos PDB y usa el puntero de instrucción actual para seleccionar los registros de ubicación adecuados para las variables y parámetros locales.

Esta captura de pantalla de la ventana Variables locales de Visual Studio muestra los parámetros y variables locales de una función en una aplicación de 64 bits optimizada. La función no está alineada, por lo que vemos tanto parámetros como variables locales.

Captura de pantalla de la ventana Variables locales en Visual Studio que muestra parámetros y variables locales para una función en una aplicación de 64 bits optimizada.

Puede usar el comando dv -v para ver las ubicaciones de los parámetros y variables locales.

Captura de pantalla de la salida del comando que muestra las ubicaciones de parámetros y variables locales mediante el comando dv -v.

Observe que la ventana Variables locales muestra correctamente los parámetros aunque se almacenen en registros.

Además de realizar el seguimiento de variables con tipos primitivos, los registros de ubicación realizan un seguimiento de los miembros de datos de las estructuras y clases locales. La siguiente salida del depurador muestra estructuras locales.

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

Estas son algunas observaciones sobre la salida del depurador anterior.

  • La estructura local My1 muestra que el compilador puede distribuir miembros de datos de estructura local para registros y ranuras de pila no contiguos.
  • La salida del comando dt My2 será diferente de la salida del comando dt _IntSum 0xefa60. No se puede suponer que la estructura local ocupará un bloque contiguo de memoria de pila. En el caso de My2, solo sum4 permanece en el bloque de pila original; los otros tres miembros de datos se mueven a los registros.
  • Algunos miembros de datos pueden tener varias ubicaciones. Por ejemplo, My2.sum2 tiene dos ubicaciones: una es registrar ECX (que elige el depurador de Windows) y la otra es 0xefa60+0x4 (la ranura de pila original). Esto podría ocurrir también para variables locales de tipo primitivo, y el depurador de Windows impone heurística precedente para determinar qué ubicación se va a usar. Por ejemplo, registrar ubicaciones siempre sobrescriba las ubicaciones de pila.

Visualización de funciones insertadas en la pila de llamadas

Durante la optimización del código, algunas funciones se colocan en línea. Es decir, el cuerpo de la función se coloca directamente en el código como una expansión de macros. No hay ninguna llamada de función y no se devuelve al autor de la llamada. Para facilitar la visualización de funciones insertadas, el compilador almacena datos en los archivos PDB que ayudan a descodificar los fragmentos de código para las funciones insertadas (es decir, secuencias de bloques de código en funciones de llamador que pertenecen a las funciones de llamada que se colocan en línea), así como las variables locales (variables locales con ámbito en esos bloques de código). Estos datos ayudan al depurador a incluir funciones insertadas como parte del desenredado de la pila.

Supongamos que compila una aplicación y obliga a que una función denominada func1 esté insertada.

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

Puede usar el comando bm para establecer un punto de interrupción en 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)]

Después de realizar un paso en func1, puede usar el comando k para ver func1 en la pila de llamadas. Puede usar el comando dv para ver las variables locales de func1. Observe que la variable num3 local se muestra como no disponible. Una variable local puede no estar disponible en código optimizado por varias razones. Es posible que la variable no exista en el código optimizado. Es posible que la variable aún no se haya inicializado o que la variable ya no se esté usando.

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>

Si observa el marco 1 en el seguimiento de la pila, puede ver las variables locales de la main función. Observe que dos de las variables se almacenan en 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

El depurador de Windows agrega datos de archivos PDB para buscar todos los lugares donde se ha colocado una función específica insertada. Puede usar el comando x para enumerar todos los sitios del autor de la llamada de una función insertada.

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

Dado que el depurador de Windows puede enumerar todos los sitios de llamada de una función insertada, puede establecer puntos de interrupción dentro de la función insertada calculando los desplazamientos de los sitios de llamada. Puede usar el comando bm (que se usa para establecer puntos de interrupción que coincidan con patrones de expresiones regulares) para establecer puntos de interrupción para las funciones insertadas.

El depurador de Windows agrupa todos los puntos de interrupción establecidos para una función insertada específica en un contenedor de puntos de interrupción. Puede manipular el contenedor de puntos de interrupción en su conjunto mediante comandos como be, bd, bc. Vea los siguientes ejemplos de comandos bd 3 y bc 3 . También puede manipular puntos de interrupción individuales. Vea el siguiente ejemplo 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

Dado que no hay instrucciones explícitas de llamada o devolución para las funciones insertadas, la ejecución paso a paso de nivel de origen es especialmente difícil para un depurador. Por ejemplo, podría pasar accidentalmente a una función insertada (si la siguiente instrucción forma parte de una función insertada), o podría entrar y salir de la misma función insertada varias veces (porque los bloques de código para la función insertada se han dividido y movido por el compilador). Para conservar la experiencia de ejecución paso a paso conocida, el depurador de Windows mantiene una pequeña pila de llamadas conceptuales para cada dirección de instrucción de código y compila una máquina de estado interna para ejecutar operaciones paso a paso, paso a paso y paso a paso por fuera. Esto proporciona una aproximación razonablemente precisa a la experiencia de ejecución paso a paso para funciones no insertadas.

Información adicional

Nota Puede usar el comando .inline 0 para deshabilitar la depuración de funciones insertadas. El comando .inline 1 habilita la depuración de funciones insertadas. Técnicas de depuración estándar

Vea también

Técnicas de depuración estándar