Slow conditional breakpoints with C++ in Visual Studio 2022
Visual Studio 2022 comes with many new fancy features, among which the ability to set temporary breakpoints (e.g. breakpoints which only trigger once) and improvements in conditional breakpoints. e.g. breakpoints which will only be triggered if certain conditions (e.g. variables values, other breakpoints) are met. I am not going to go through these features again here as they have been described in details in this Microsoft article but I do want to share my experience using them, to judge how useful they actually are in your daily development jobs.
I am debugging a DOS device driver which loads itself into 2 different segments, 0x0CEE and 0x0B8C. Using DOSBox-X which compiles well under Visual Studio 2019 and Visual Studio 2022, I want to find out what causes the instructions pointer to change between these two segments, e.g. when CS:IP changes from 0x0CEE:XXXX to 0x0B8C:XXXX and vice versa. Without conditional breakpoints, this can be done by adding if statements which will write the relevant log messages if these conditions are met, and by setting a breakpoint to trigger on these newly added statements. The changes can be made at CPU_Core_normal_Run(void) in core_normal.cpp (around line 162) and the breakpoint set at OutputDebugStringA:
Once the right conditions are met, the breakpoint is immediately hit. Using the Watch window, I can examine the value of the x86 registers (AX,BX,CX,DX) at that moment:
In recent versions of Visual Studio, the process is simpler as you do not need to modify the code. The triggering condition and the message to be written can be set in the breakpoint itself.
This works immediately and the log message is printed to the output window as soon as the breakpoint is hit. However, I also realize that my emulated DOS machine now boots up very slowly, It takes almost 120 seconds for DOSBox to reach the “Starting MS-DOS” stage whereas it took less than 5 seconds previously. Tweaking various DOSBox speed settings does not help but disabling the conditional breakpoint and the original execution speed is restored. I worked around it by enabling the breakpoint just before I started my DOS program within DOSBox, which did not help much, because execution would still slow down immediately. The original method of manually modifying the code did not slow down the boot up speed.
After further research I found the reason. CPU_Core_Normal_Run() is no normal function. Ignoring other circumstances (instructions caching etc.) which won’t be covered here, CPU_Core_Normal_Run() is called as soon as an instruction that has been disassembled by DOSBox is ready to be executed. The original 8088 has a speed of 4.77MHz (about 0.2 MIPS), which means that this function will be executed more than 200,000 times per second. On the default DOSBox configuration emulating a 486, this function will be called millions of times per second. Likely, adding the conditional breakpoint introduces some processing overhead which only becomes noticeable if a function is called an excessive number of times.
If your function is not executed that frequently, conditional breakpoints will work just fine, and is much more convenient then the other alternative of modifying code to add a conditional print statement.