Finding Bugs with AddressSanitizer: MSVC Compiler
Special thanks to Aaron Gorenstein for authoring this blog post.
The AddressSanitizer (ASan) is generally available for MSVC since the recently-released Visual Studio 2019 version 16.9. We’ve already shown how easy it can be to find bugs in even production-ready code like EASTL. Here I’ll share an example of how it found a real bug in the MSVC compiler itself.
The idea was straightforward: ASan finds bugs, and we’re always interested in finding bugs in the compiler. Just like you can turn ASan on in your projects and run your tests, we’ve been turning on ASan on our project (the compiler) and running it on our tests. Sure enough, this found bugs.
Building our Binary with ASan
It was easy to turn on ASan in our build system. We’ve documented ways to turn ASan on in common build scenarios. In our case, I added
/fsanitize=address to the build’s cl.exe command line, and our old, ever-evolving build system needed the extra manual step of specifying where our extension library lived.
That was all it took! I now was able to build my binary, c2.dll, “just like normal”, but now it had lots of excellent ASan instrumentation imbued to help find bugs. I was ready to run our inner-ring test suite and see if anything cropped up.
Finding the Bug
Our inner-test loop is about 4,000 separate C++ files, containing a mix of real-world-code, synthetic tests, benchmarks, and regression tests. We have a home-made test-runner that is only accessible from the command-line. Running it, we almost passed, but hit exactly 1 failure. I looked in our log file and see the characteristic trace:
A couple things I would like to highlight:
- The error reported is “stack-buffer-underflow”: this is a stack ASan is able to find both stack and heap issues.
- Note the line “stack of thread T3”. As that suggests, there is also a T1 and T2 (and more): c2.dll executes many threads in parallel. ASan can handle multiple threads like that no problem!
Most importantly: ASan never has false positives. This trace I found is definitely a bug, so I already know I found something to fix.
Fortunately, the triggering input is a single file. I can easily repeat the command that repro’s the bug manually. To be clear, at this point this was all I needed to do to hit the issue:
I’ve truncated the output, but the terminal contained the full ASan command-line diagnostics. I could use that information (starting with the stack-trace you can see above) to investigate the issue. However, I like examining these in the full IDE and debugging experience. With this command line, I can reproduce the ASan issue but attach it to the debugger:
Starting the debugger-attached version of my binary, I see:
The IDE is able to provide a wealth—interactively—of information about what’s going on the moment a memory violation is detected. You can see the ASan issue is reported as an exception, bringing me right to the correct line number, along with my familiar debugger call-stack and everything else. The output window is still available for those accustomed to it.
Any guesses of where the bug may be lurking?
sz” likely stands for “size”. Observe recall how ASan reports “stack buffer underflow”.
Fixing the Bug
Examining the value of
sz made it clear enough:
MscIsFloatOrVectorConstant returns the size of the constant if it is found, and 0 otherwise. In this buggy case, it returns 0, and we underflow the array field in the function-local struct
vval. The fix is equally straightforward: following idioms in the rest of the file, we simply add a check for that before line 16828. This fix has been integrated and will be included in version 16.10.
This particular bug is very unlikely to strike “in the wild”: the stack would need to have garbage values in just-the-right way (to pass the condition on line 16831). However, in theory this bug—and more generally, bugs just like it—could lead to an improper optimization in your code. That is among the worst sort of bug a compiler can have: silent-bad-codegen. I’m very pleased to have squashed this one. I’m also very glad to have been able to share just how easy ASan can make bugfixing with you.
We don’t typically write blog posts about fixing a bug in the compiler, but of course the real story is just how easily and effectively ASan helps find and fix bugs:
- Our bespoke, command-line driven build system needed just a few lines of changes to integrate the build-with-ASan option.
- Once built, testing our binary was seamless: I ran my typical inner-dev-loop test suite.
- Once it found an issue, it was equally seamless to repeat the steps in our IDE’s debugger, pointing me right to the source line to examine.
- In more sophisticated circumstances, consider the ability to save a dump file!
- The exact source line, coupled with ASan’s ability to characterize the issue (a stack underflow), made the investigation quick and easy. No long hours or brilliant insight needed: of course I still had to confirm and actually fix the issue, but compared to a traditional bugfix so much of the investigation was short-circuited.
It is exactly the speed, effectiveness, and simplicity of ASan that I hope this story helps illustrate. Moreover, and most compellingly to me: ASan found a memory violation that hadn’t yet manifested as bad behavior in our program. It could manifest, but here we were able to pin it down and squash it without an expensive, more indirect investigation—and hopefully without it ever having affected our customers!
Try out AddressSanitizer for Windows
To get started with this experience, check out our AddressSanitizer documentation for MSVC and Visual Studio as well as our announcement blog post: Address Sanitizer for MSVC Now Generally Available.
ASan for MSVC was done due to feedback from developers like you. If you have suggestions on the future of undefined-behavior-, memory-, thread-, or other-sanitizers, please share them as a suggestion on Developer Community! If you suspect you’ve hit an issue or bug, please also don’t hesitate to open a ticket on Developer Community!