Depuración dinámica de C++ (versión preliminar)
Importante
La depuración dinámica de C++ está actualmente en versión preliminar. Esta información se relaciona con una característica de versión preliminar que podría modificarse sustancialmente antes de la versión. Microsoft no ofrece ninguna garantía, expresada o implícita, con respecto a la información proporcionada aquí.
Esta característica en versión preliminar, disponible a partir de Visual Studio 2022, versión 17.14 Preview 2, solo se aplica a proyectos x64.
Con la depuración dinámica de C++, puede depurar código optimizado como si no estuviera optimizado. Esta característica es útil para los desarrolladores que requieren las ventajas de rendimiento del código optimizado, como los desarrolladores de juegos que necesitan altas velocidades de fotogramas. Con la depuración dinámica de C++, puede disfrutar de la experiencia de depuración de código no optimizado sin sacrificar las ventajas de rendimiento de las compilaciones optimizadas.
La depuración de código optimizado presenta desafíos. El compilador cambia la posición y reorganiza las instrucciones para optimizar el código. El resultado es un código más eficaz, pero significa lo siguiente:
- El optimizador puede quitar variables locales o moverlas a ubicaciones desconocidas para el depurador.
- Es posible que el código dentro de una función ya no se alinee con el código fuente cuando el optimizador combina bloques de código.
- Los nombres de función de las funciones de la pila de llamadas podrían ser incorrectos si el optimizador combina dos funciones.
En el pasado, los desarrolladores trataron estos problemas y otros cuando estaban en proceso de depuración de código optimizado. Ahora se eliminan estos desafíos porque con la depuración dinámica de C++ se puede depurar paso a paso en código optimizado como si no estuviera optimizado.
Además de generar los archivos binarios optimizados, la compilación con /dynamicdeopt
genera archivos binarios no optimizados que se usan durante la depuración. Al agregar un punto de interrupción o al entrar a una función (incluyendo las funciones __forceinline
), el depurador carga el binario no optimizado. A continuación, puede depurar el código no optimizado para la función en lugar del código optimizado. Puede depurar como si depurase código no optimizado mientras todavía obtiene las ventajas de rendimiento del código optimizado en el resto del programa.
Probar la depuración dinámica de C++
En primer lugar, vamos a revisar cómo es depurar código optimizado. A continuación, puede ver cómo la depuración dinámica de C++ simplifica el proceso.
Cree un nuevo proyecto de aplicación de consola de C++ en Visual Studio. Reemplace el contenido del archivo ConsoleApplication.cpp por el código siguiente:
// Code generated by GitHub Copilot #include <iostream> #include <chrono> #include <thread> using namespace std; int step = 0; const int rows = 20; const int cols = 40; void printGrid(int grid[rows][cols]) { cout << "Step: " << step << endl; for (int i = 0; i < rows; ++i) { for (int j = 0; j < cols; ++j) { cout << (grid[i][j] ? '*' : ' '); } cout << endl; } } int countNeighbors(int grid[rows][cols], int x, int y) { int count = 0; for (int i = -1; i <= 1; ++i) { for (int j = -1; j <= 1; ++j) { if (i == 0 && j == 0) { continue; } int ni = x + i; int nj = y + j; if (ni >= 0 && ni < rows && nj >= 0 && nj < cols) { count += grid[ni][nj]; } } } return count; } void updateGrid(int grid[rows][cols]) { int newGrid[rows][cols] = { 0 }; for (int i = 0; i < rows; ++i) { for (int j = 0; j < cols; ++j) { int neighbors = countNeighbors(grid, i, j); if (grid[i][j] == 1) { newGrid[i][j] = (neighbors < 2 || neighbors > 3) ? 0 : 1; } else { newGrid[i][j] = (neighbors == 3) ? 1 : 0; } } } // Copy newGrid back to grid for (int i = 0; i < rows; ++i) { for (int j = 0; j < cols; ++j) { grid[i][j] = newGrid[i][j]; } } } int main() { int grid[rows][cols] = { 0 }; // Initial configuration (a simple glider) grid[1][2] = 1; grid[2][3] = 1; grid[3][1] = 1; grid[3][2] = 1; grid[3][3] = 1; while (true) { printGrid(grid); updateGrid(grid); std::this_thread::sleep_for(std::chrono::milliseconds(100)); cout << "\033[H\033[J"; // Clear the screen step++; } return 0; }
Cambie la lista desplegable Configuraciones de solución a Lanzamiento. Asegúrese de que la lista desplegable de la plataforma de soluciones esté establecida en x64.
Recompile seleccionando Compilar>Recompilar solución.
Establezca un punto de interrupción en la línea 55,
int neighbors = countNeighbors(grid, i, j);
enupdateGrid()
. Ejecute el programa.Al alcanzar el punto de interrupción, consulte la ventana Variables locales. En el menú principal, seleccione Depurar>Windows>Variables locales. Observe que no puede ver el valor de
i
oj
en la ventana de Variables locales. El compilador las ha optimizado eliminándolas.Intente establecer un punto de interrupción en la línea 19,
cout << (grid[i][j] ? '*' : ' ');
enprintGrid()
. No puede. Este comportamiento se espera porque el compilador ha optimizado el código.
Detenga el programa y habilite la depuración dinámica de C++ e inténtelo de nuevo.
En Explorador de soluciones, haga clic con el botón derecho en el proyecto y seleccione Propiedades para abrir las páginas de propiedades del proyecto.
Seleccione Advanced>Use C++ Dynamic Debuggingy cambie la configuración a Sí.
La página de propiedades se abre en la sección de Propiedades de configuración > Avanzado > Usar depuración dinámica de C++. La propiedad se establece en Sí.
En este paso se agrega el conmutador
/dynamicdeopt
al compilador y al enlazador. En segundo plano, también desactiva los conmutadores de optimización de C++/GL
y/OPT:ICF
. Esta configuración no sobrescribe los conmutadores que agregó manualmente a la línea de comandos u otros conmutadores de optimización que ya están establecidos, como/O1
.Recompile seleccionando Compilar>Recompilar solución. Aparece el código de diagnóstico de compilación
MSB8088
, que indica que la depuración dinámica y la optimización de todo el programa son incompatibles. Este error significa que toda la optimización del programa (/GL
) se desactivó automáticamente durante la compilación.Puede desactivar manualmente toda la optimización del programa en las propiedades del proyecto. Seleccione Propiedades de configuración>Avanzado>Optimización de todo el programay cambie la configuración a Desactivado. Ahora
MSB8088
se trata como una advertencia, pero podría tratarse como un error en una versión futura de Visual Studio.Vuelva a ejecutar la aplicación.
Ahora, cuando llegue al punto de interrupción en la línea 55, verá los valores de
i
yj
en la ventana de Variables locales. La ventana de pila de llamadas muestra queupdateGrid()
está desoptimizado y el nombre del archivo eslife.alt.exe
. Este binario alternativo se usa para depurar código optimizado.Se muestra un punto de interrupción en la función updateGrid. La pila de llamadas muestra que la función está desoptimizada y el nombre de archivo es life.alt.exe. La ventana Variables locales muestra los valores de i y j y las demás variables locales de la función.
La función
updateGrid()
se desoptimiza a petición porque se establece un punto de interrupción en ella. Si se pasa sobre una función optimizada mientras se depura, no se desoptimiza. Si pasa por en una función, se desoptimiza. La forma principal de hacer que una función se desoptimice es si se establece un punto de interrupción en ella o se introduce en ella.También puede desoptimizar una función en la ventana Pila de llamadas. Haga clic con el botón derecho en la función o en un grupo seleccionado de funciones y seleccione Deoptimize en la entrada siguiente. Esta característica es útil cuando desea ver variables locales en una función optimizada para la que no ha establecido un punto de interrupción en otra parte de la pila de llamadas. Las funciones que se desoptimizan de esta manera se agrupan en la ventana de puntos de interrupción como un grupo de puntos de interrupción denominado Funciones Desoptimizadas. Si elimina el grupo de puntos de interrupción, las funciones asociadas vuelven a su estado optimizado.
Uso de puntos de interrupción condicionales y dependientes
Intente volver a establecer un punto de interrupción en la línea 19,
cout << (grid[i][j] ? '*' : ' ');
enprintGrid()
. Ahora funciona. Al establecer un punto de interrupción en la función, esta se desoptimiza para que puedas depurarla normalmente.Haga clic con el botón derecho en el punto de interrupción de la línea 19, seleccione Condicionesy establezca la condición en
i == 10 && j== 10
. A continuación, marque la casilla Habilitar solo cuando se alcance el siguiente punto de interrupción:. Seleccione el punto de interrupción de la línea 55 en la lista desplegable. Ahora el punto de interrupción de la línea 19 no se activa hasta que el punto de interrupción de la línea 50 se activa primero, y luego cuandogrid[10][10]
está a punto de mostrarse en la consola.El punto es que puede establecer puntos de interrupción condicionales y dependientes en una función optimizada y usar variables locales y líneas de código que en una compilación optimizada podría no estar disponible para el depurador.
Se muestra un punto de interrupción condicional en la línea 19, cout < < (grid[i][j] ? '*' : ' ');. La condición se establece en i == 10 && j== 10. La casilla para habilitar solo cuando se alcanza el siguiente punto de interrupción está seleccionada. La lista desplegable de puntos de interrupción se establece en life.cpp línea 55.
Continúe ejecutando la aplicación. Cuando se alcanza el punto de interrupción en la línea 19, puede hacer clic con el botón derecho en la línea 15 y seleccionar Establecer instrucción siguiente para volver a ejecutar el bucle.
Se alcanza un punto de interrupción condicional y dependiente en la línea 19, cout < < (grid[i][j] ? '*' : ' ');. La ventana Variables locales muestra los valores de i y j y las demás variables locales de la función. La ventana Pila de llamadas muestra que la función está desoptimizada y que el nombre de archivo es life.alt.exe.
Elimine todos los puntos de interrupción para devolver funciones desoptimizadas a su estado optimizado. En el menú principal de Visual Studio, seleccione Depurar>Eliminar todos los puntos de interrupción. A continuación, todas las funciones vuelven a su estado optimizado.
Si agrega puntos de interrupción a través de la ventana Pila de llamadas Desoptimizar en la siguiente entrada, lo que no hicimos en este tutorial, puede hacer clic con el botón derecho del ratón en el grupo Funciones optimizadas y seleccionar Eliminar para revertir solo las funciones de ese grupo a su estado optimizado.
La ventana Puntos de interrupción muestra el grupo Funciones desoptimizadas. El grupo está seleccionado y el menú contextual está abierto con Eliminar Grupo de Puntos de Interrupción seleccionado.
Desactivar la depuración dinámica de C++
Es posible que necesite depurar código optimizado sin que se desoptimice, o poner un punto de ruptura en el código optimizado y que el código permanezca optimizado cuando el punto de ruptura se active. Hay varias maneras de desactivar la depuración dinámica o evitar que se desoptimice el código al alcanzar un punto de interrupción:
- En el menú principal de Visual Studio, seleccione Herramientas>Opciones>Depuración>General. Desmarque la casilla Desoptimizar automáticamente las funciones depuradas cuando sea posible (.NET 8+, Depuración dinámica de C++). La próxima vez que se inicie el depurador, el código permanece optimizado.
- Muchos puntos de interrupción de depuración dinámica son dos puntos de interrupción: uno en el binario optimizado y otro en el binario no optimizado. En la ventana Puntos de interrupción, seleccione Mostrar Columnas>Función. Borre el punto de interrupción asociado al binario
alt
. El otro punto de interrupción del par se interrumpe en el código optimizado. - Al depurar, en el menú principal de Visual Studio, seleccione Depurar>Windows>Desensamblar. Asegúrese de que tiene el foco. Al entrar en una función a través de la ventana de Desensamblar, la función no se desoptimizará.
- Deshabilite la depuración dinámica por completo sin pasar
/dynamicdeopt
acl.exe
,lib.exe
ylink.exe
. Si consume bibliotecas de terceros y no puede recompilarlas, no pase/dynamicdeopt
durante ellink.exe
final para deshabilitar la Depuración dinámica para ese binario. - Para deshabilitar rápidamente la depuración dinámica de un binario único (por ejemplo,
test.dll
), cambie el nombre o elimine el binario correspondiente aalt
(por ejemplo,test.alt.dll
). - Para deshabilitar la depuración dinámica para uno o varios archivos de
.cpp
, no pase/dynamicdeopt
al compilarlos. El resto del proyecto se compila con depuración dinámica.
Habilitación de la depuración dinámica de C++ en Unreal Engine
Unreal Engine 5.6 admite la depuración dinámica de C++ para la herramienta de compilación de Unreal y el Acelerador de compilación de Unreal. Hay dos maneras de habilitarlo:
Modifique el archivo
Target.cs
del proyecto para que contengaWindowsPlatform.bDynamicDebugging = true
.Use la configuración del Editor de desarrollo de y modifique
BuildConfiguration.xml
para incluir:<WindowsPlatform> <bDynamicDebugging>true</bDynamicDebugging> </WindowsPlatform>
Para Unreal Engine 5.5 o versiones anteriores, selecciona los cambios del Unreal Build Tool del GitHub e intégralos en tu repositorio. A continuación, habilite bDynamicDebugging
como se indica anteriormente. También debe usar el Acelerador de compilación de Unreal de Unreal Engine 5.6. Use los bits más recientes de ue5-main o deshabilite UBA agregando lo siguiente a BuildConfiguration.xml
:
<BuildConfiguration>
<bAllowUBAExecutor>false</bAllowUBAExecutor>
<bAllowUBALocalExecutor>false</bAllowUBALocalExecutor>
</BuildConfiguration>
Para obtener más información sobre cómo se compila Unreal Engine, consulte Configuración de compilación.
Solución de problemas
Si los puntos de ruptura no se alcanzan en funciones no optimizadas:
Si sale de un marco
[Deoptimized]
, podría estar en código optimizado, a menos que la función que llamó haya sido desoptimizada debido a un punto de interrupción en ella o que haya entrado en la función que llamó al ir hacia la función actual.Asegúrese de que los archivos
alt.exe
yalt.pdb
se han compilado. Paratest.exe
ytest.pdb
,test.alt.exe
ytest.alt.pdb
deben existir en el mismo directorio. Asegúrese de que los modificadores de compilación correctos se establecen según este artículo.Existe una entrada
debug directory
entest.exe
que usa el depurador para buscar el binarioalt
que se va a usar para depuración desoptimizada. Abra un símbolo del sistema de Visual Studio nativo de x64 y ejecutelink /dump /headers <your executable.exe>
para ver si existe una entrada dedeopt
. Aparece una entradadeopt
en la columnaType
, como se muestra en la última línea de este ejemplo:Debug Directories Time Type Size RVA Pointer -------- ------- -------- -------- -------- 67CF0DA2 cv 30 00076330 75330 Format: RSDS, {7290497A-E223-4DF6-9D61-2D7F2C9F54A0}, 58, D:\work\shadow\test.pdb 67CF0DA2 feat 14 00076360 75360 Counts: Pre-VC++ 11.00=0, C/C++=205, /GS=205, /sdl=0, guardN=204 67CF0DA2 coffgrp 36C 00076374 75374 67CF0DA2 deopt 22 00076708 75708 Timestamp: 0x67cf0da2, size: 532480, name: test.alt.exe
Si la entrada del directorio de depuración de
deopt
no está ahí, confirme que está pasando/dynamicdeopt
acl.exe
,lib.exe
ylink.exe
.La desoptimización dinámica no funcionará de forma coherente si
/dynamicdeopt
no se pasa acl.exe
,lib.exe
ylink.exe
para todos los.cpp
,.lib
y archivos binarios. Confirme que los interruptores adecuados están configurados al compilar el proyecto.
Para más información sobre los problemas conocidos, vea Depuración dinámica de C++: depuración completa para compilaciones optimizadas.
Si las cosas no funcionan según lo previsto, abra un ticket en la Comunidad de Desarrolladores . Incluya la mayor cantidad de información posible sobre el problema.
Notas generales
IncrediBuild 10.24 admite compilaciones de depuración dinámica de C++.
Las funciones insertadas se desoptimizan a petición. Si establece un punto de interrupción en una función insertada, el depurador desoptimiza la función y su llamador. El punto de interrupción se activa donde se espera, como si el programa se compilara sin optimizaciones del compilador.
Una función permanece desoptimizada aunque deshabilite los puntos de interrupción dentro de ella. Debe quitar el punto de interrupción de la función para revertir a su estado optimizado.
Muchos puntos de interrupción de depuración dinámica son dos puntos de interrupción: uno en el binario optimizado y otro en el binario no optimizado. Por este motivo, verá más de un punto de interrupción en la ventana Puntos de interrupción.
Las marcas del compilador que se usan para la versión desoptimizada son las mismas que las marcas que se usan para la versión optimizada, excepto para las marcas de optimización y /dynamicdeopt
. Este comportamiento significa que las marcas que establezca para definir macros, etc., también se establecen en la versión desoptimizada.
El código desoptimizado no es el mismo que el código de depuración. El código desoptimizado se compila con las mismas marcas de optimización que la versión optimizada, por lo que no se incluyen aserciones y otro código que dependen de la configuración específica de depuración.
Integración del sistema de compilación
La depuración dinámica de C++ requiere que los marcadores del compilador y del enlazador se establezcan de una manera determinada. En las siguientes secciones, se describe cómo establecer una configuración específica para la depuración dinámica que carece de opciones en conflicto.
Si el proyecto se compila con el sistema de compilación de Visual Studio, una buena manera de realizar una configuración de depuración dinámica es usar Configuration Manager para clonar la configuración de versión o depuración y realizar cambios para dar cabida a la depuración dinámica. En las dos secciones siguientes se describen los procedimientos.
Creación de una nueva configuración de versión
En el menú principal de Visual Studio, seleccione Build>Configuration Manager para abrir Configuration Manager.
Seleccione la lista desplegable de Configuración y, a continuación, seleccione <Nuevo...>.
En Configuration Manager, en Contextos del proyecto, la lista desplegable Configuración está abierta y
está resaltado. Se abre el cuadro de diálogo Nueva configuración de solución. En el campo Nombre, escriba un nombre para la nueva configuración, como
ReleaseDD
. Asegúrese de que Copiar configuración de: está establecido en Versión. A continuación, seleccione Aceptar para crear la nueva configuración.El campo Nombre se establece en ReleaseDD. La lista desplegable Copiar configuración de: está establecida en Versión.
La nueva configuración aparece en la lista desplegable Configuración de la solución activa. Selecciona Cerrar.
Con la lista desplegable Configuración establecida en ReleaseDD, haga clic con el botón derecho en su proyecto en Explorador de soluciones y seleccione Propiedades.
En Propiedades de configuración>Avanzadas, establezca Usar depuración dinámica de C++ en Sí.
Asegúrese de que Optimización de todo el programa está establecido en No.
La página de propiedades se abre en Propiedades de configuración > Avanzadas. Use la depuración dinámica de C++. La propiedad se establece en Sí. Optimización de todo el programa está establecido en No.
En Propiedades de configuración>Enlazador>Optimización, asegúrese de que Habilitar plegado COMDAT está establecido en No (/OPT:NOICF).
La página de propiedades se abre en Propiedades de configuración > Enlazador > Optimización > Habilitar plegado CMDAT. La propiedad se establece en No (/OPT:NOICF).
Esta configuración agrega el conmutador /dynamicdeopt
al compilador y al enlazador. Con los modificadores de optimización de C++ /GL
y /OPT:ICF
también desactivados, ahora puede compilar y ejecutar el proyecto en la nueva configuración cuando desee una compilación de versión optimizada que puede usar con la depuración dinámica de C++.
Puede agregar a esta configuración otros conmutadores que utilice con sus compilaciones comerciales, de modo que siempre tenga activados o desactivados exactamente los conmutadores que espera cuando usa la depuración dinámica. Para más información sobre los modificadores que no se deben usar con la depuración dinámica, consulte Opciones incompatibles.
Para obtener más información sobre las configuraciones en Visual Studio, vea Crear y editar configuraciones.
Creación de una nueva configuración de depuración
Si quiere usar archivos binarios de depuración, pero quiere que se ejecuten más rápido, puede modificar la configuración de depuración.
En el menú principal de Visual Studio, seleccione Compilar>Administrador de configuración para abrir Configuration Manager.
Seleccione la lista desplegable de configuración y, a continuación, seleccione <Nuevo...>.
En Configuration Manager, en la parte de Contextos del proyecto de la ventana, la lista desplegable de Configuración está abierta y
está resaltado. Se abre el cuadro de diálogo Nueva configuración del proyecto. En el campo Nombre, escriba un nombre para la nueva configuración, como DebugDD. Asegúrese de que Copiar configuración de: está establecido en Depurar. A continuación, seleccione Aceptar para crear la nueva configuración.
El campo de nombre se establece en DebugDD. La lista desplegable Copiar configuración de: está establecida en Depurar.
La nueva configuración aparece en la lista desplegable de configuración activa de la solución . Selecciona Cerrar.
Con la lista desplegable Configuración establecida en DebugDD, haga clic con el botón derecho en su proyecto en Explorador de soluciones y seleccione Propiedades.
En Propiedades de configuración>>optimización de C/C++, active las optimizaciones que desee. Por ejemplo, podría establecer Optimización en Maximizar velocidad (/O2).
En C/C++>Generación de código, establezca Comprobaciones básicas en tiempo de ejecución en Predeterminado.
En C/C++>General, deshabilite Admitir depuración de Solo mi código.
Establezca Formato de información de depuración en Base de datos de programa (/Zi).
Puede agregar otros conmutadores que use con las compilaciones de depuración a esta configuración para que siempre tenga exactamente los conmutadores activados o desactivados que espera al usar la depuración dinámica. Para más información sobre los modificadores que no se deben usar con la depuración dinámica, consulte Opciones incompatibles.
Para obtener más información sobre las configuraciones en Visual Studio, vea Crear y editar configuraciones.
Consideraciones sobre el sistema de compilación personalizado
Si tiene un sistema de compilación personalizado, asegúrese de que:
- Pase
/dynamicdeopt
acl.exe
,lib.exe
ylink.exe
. - No utilice
/ZI
, ninguna de las banderas de/RTC
, o/JMC
.
Para distribuidores de compilación:
- Para un proyecto denominado
test
, el compilador generatest.alt.obj
,test.alt.exp
,test.obj
ytest.exp
. El enlazador generatest.alt.exe
,test.alt.pdb
,test.exe
ytest.pdb
. - Debe implementar el nuevo binario del conjunto de herramientas
c2dd.dll
junto conc2.dll
.
Opciones incompatibles
Algunas opciones del compilador y del enlazador no son compatibles con la depuración dinámica de C++. Si activa la depuración dinámica de C++ mediante la configuración del proyecto de Visual Studio, las opciones incompatibles se desactivan automáticamente a menos que se establezcan específicamente en la configuración de opciones de línea de comandos adicionales.
Las siguientes opciones del compilador no son compatibles con la depuración dinámica de C++:
/GH
/GL
/Gh
/RTC1
/RTCc
/RTCs
/RTCu
/ZI (/Zi is OK)
/ZW
/clr
/clr:initialAppDomain
/clr:netcore
/clr:newSyntax
/clr:noAssembly
/clr:pure
/clr:safe
/fastcap
/fsanitize=address
/fsanitize=kernel-address
Las siguientes opciones del enlazador no son compatibles con la depuración dinámica de C++:
/DEBUG:FASTLINK
/INCREMENTAL
/OPT:ICF You can specify /OPT:ICF but the debugging experience may be poor
Consulte también
marca del compilador /dynamicdeopt (versión preliminar)
Marca del enlazador /DYNAMICDEOPT (versión preliminar)
Depuración dinámica de C++: depuración completa para compilaciones optimizadas
Depuración de código optimizado