(C++ x86) /Og renders function parameters in catch blocks unusable - by Krishty

Status : 

  Fixed<br /><br />
		This item has been fixed in the current or upcoming version of this product.<br /><br />
		A more detailed explanation for the resolution of this particular item may have been provided in the comments section.

Sign in
to vote
ID 624668 Comments
Status Closed Workarounds
Type Bug Repros 0
Opened 11/25/2010 11:10:13 AM
Access Restriction Public


With /Og (global optimization) enabled (default in Release builds), a function's parameters change their addresses if, and only if, the function catches an exception.

The addresses of local variables remain unaltered. All function parameters become invalid upon entering the catch block; reading or writing them typically results in an access violation or in a corruption of the program's internal state.

In one case, the function parameter's addresses were shifted by +20 bytes, in another, they were rebased to 0x8bffffa0.

Disabling global optimization fixes the problem, but results in poor performance.
Sign in to post a comment.
Posted by Ian [MSFT] on 12/21/2010 at 2:00 PM
The problem is that the compiler is using EBX as a frame pointer register and not restoring the value of EBX in the catch.
The reason that the restore of EBX is missing is that it has inadvertently been optimized away.

A fix is in progress and will be included in the next major release of Visual Studio.

In the meantime this problem will be at risk of showing up whenever a parameter is accessed in a catch when the parameter pointer register is EBX. The compiler uses EBX as the parameter pointer register if the frame is dynamically aligned (e.g. because the function contains a local that requires than 4-byte alignment).

You can continue to run with optimizations disabled for functions that fit this description. Another possiblity is to access the parameters in a way that forces the compiler to not rely on EBX in the catch handler. For example, copy them into a local (probably a volatile one to avoid copy propagation) or copy them to a global variable. Both ways would avoid the compiler using EBX incorrectly in the catch handler.
Posted by Ian [MSFT] on 12/19/2010 at 11:55 AM
Thanks very much for reducing this down. I've been able to reproduce the crash and i'm looking into the cause now.
Based on what you've found and reported so far it looks like a bad interaction between EH and dynamic alignment on x86.
Posted by Krishty on 12/18/2010 at 10:50 AM
Reproduced under 2010 RTM and 2010 SP1. Not reproducable under 2005 and 2008; but it's up to you to confirm that after you have identified the exact cause.

Also, the sample is very sensitive for inline function expansion and other optimizations. However, compiling with nothing but /Og /EHsc will definitely reproduce the issue.
Posted by Krishty on 12/17/2010 at 8:14 PM
It's the alignment (this also explains why it didn't show up in the x64 compiler). Perfectly reproducing with /Og here:

struct __declspec(align(8)) Aligned { // must be aligned, but didn't test other values than 8
    int dummyMember;

    Aligned() {
        dummyMember = 0; // crucial

    int returnAnInt() const {
        return 0;    // also tried a function with a volatile int being read
                    // from the object, but it didn't work


void badFunction(
    Aligned & willBecomeInvalid
) {
    Aligned willRemainValid; // crucial
    // parameter and local valid here
    try {
        // still valid here
        throw 0;
    } catch(...) {
        // here, the parameter's address has changed significandly
        volatile int x = willBecomeInvalid.returnAnInt();
        // if the parameter is rebased to the address of the catch block
        //    (which was the case in the project I sent you), I'm now
        //    overwriting the catch block with NOPs. Great if you do string
        //    processing with user input here :)
        willBecomeInvalid.dummyMember = 0x1F1F1F1F;

int main() {

    Aligned param;

    return 0;

good night, and good luck.
Posted by Krishty on 12/17/2010 at 6:09 PM
Good news -- now that all optimzation except /Og is disabled, the bug is by far more stable than before. I could remove 95 % of the program, greatly simplify the involved types. Currently, it looks like this:

memory::CReadablyMappedFile badFunction(
) {
    try {
        return memory::CReadablyMappedFile(WILL_BECOME_INVALID); // will throw
    } catch(...) {
        return memory::CReadablyMappedFile(WILL_BECOME_INVALID); // will never be reached

With some more work on the two classes involved, I hope I can finally build a simple test case.
Posted by Krishty on 12/17/2010 at 5:31 PM
The code I sent you was already prepared for easy debugging -- the function is called quite early and the call is designed to always raise the access violation. If you have the June 2010 DirectX SDK installed, you should be able to link the repro to an exe and use the enclosed pdb and .i to jump into the function and browse its source and assembly code before it crashes.

If that is not possible, or if the preprocessed source code is too exhaustive to debug, I'll send you a new repro with the original source code of the affected objects. I'll also try to simplify the program further, but this is difficult as it sometimes has an impact on which of the catch handlers crashes.
Posted by Ian [MSFT] on 12/17/2010 at 5:21 PM
By inspection the code generated by the compiler looks correct. I have an additional request that would help. Would you be able to provide a stand alone test case that i can run that demonstrates the failure? Without being able to execute the code i'm not certain i can figure out what is going wrong.
Posted by Ian [MSFT] on 12/16/2010 at 10:02 AM
Thanks for all of the information. i'm using the data you provided to investigate now.
Posted by Krishty on 12/16/2010 at 9:36 AM
The parameter is addressed in the whole function as [ebx+0Ch]. When the stack unwinding finishes and the catch block is reached, ebx will not point to the stack any more but will hold a completely other value. At first glance I thought this value would be an unrelated temporary from one of the sub functions or from the exception handlers, but turns out it is the exact address of the first instruction of the catch block.

So, instead of accessing the parameter, the program accesses its own instructions. Since the .text section is read-only, an access violation is raised. But if the .text section wasn't read-only, the function would overwrite the catch block.

I'll investigate further to find out where exactly ebx is overwritten but not restored.
Posted by Krishty on 12/15/2010 at 6:00 PM
Okay, you find the link-repro (no global optimization) as well as the updated .i (line 339924) in simplified.zip.

The compiler command line has been simplified to
/Zi /nologo /W4 /WX- /MP /Ox /Oi /Oy- /D "NDEBUG" /D "CRIC_USE_XNA_MATH" /D "CRIC_BIEDER_CORE_STATIC_BUILD" /Gm- /EHsc /GS- /fp:precise /Zc:wchar_t /Zc:forScope /GR- /Fp"..\..\..\Intermediate\Bieder\Core\x86S\Core.pch" /Fo"..\..\..\Intermediate\Bieder\Core\x86S\" /Fd"..\..\..\Intermediate\Bieder\Core\x86S\vc100.pdb" /Gd /errorReport:queue

The linker command line has been simplified, you find it in the zip, too.

I reproduced the problem in the files I sent you (the parameter is moved to 0x8b000046 now). Hope you find out what's wrong.
Posted by Krishty on 12/15/2010 at 4:45 PM
Just wanted to say: I've been debugging since yesterday and I disabled as many optimizations as I could, including /GL. The problem persists.

I was able to find the parameter's address on the stack -- at least I think so, my assembler skills are limited. But when I wrote another value there, the parameter's address in the debugger would also change, so I think I hit it.
I set a data breakpoint to this address, and the value is NOT overwritten while the stack is unwound -- it is untouched over the whole execution time of the function. Seems like the code in the catch block looks for the parameter in the wrong place.

Maybe that's not surprising; I'm not familiar with the stack internals of exception handling, but I thought it might be of interest for you.

Give me some time to disable some more optimizations and to disable /DYNAMICBASE, then I'll send you your repro (if you still need it, now that I'm not using /GL any more).
Posted by Ian [MSFT] on 12/15/2010 at 4:22 PM
I see you are using /GL on the command line. The /GL switch enables link-time code generation, so as it's name implies code generation doesn't occur until the link step. I should have asked this earlier to save time.

In order to reproduce a /GL issue i'm going to need a link-repro. Fortunately this is pretty easy to do.

0. Build your project as normal
1. Open a VC++ command shell
2. md "c:\linkrepro"
3. set LINK_REPRO=c:\linkrepro
4. execute the link command for your project (the same one you posted)
5. set LINK_REPRO=

This will create a link repro in the directory you created in step #2. You can verify that this reproduces your issue using the next two steps:

6. pushd c:\linkrepro
7. link.exe @link.rsp

Once you're satsified with the link repro, package the entire c:\linkrepro directory into a .zip file and send me that.

Posted by Krishty on 12/14/2010 at 3:27 PM
The file was originally compiled to a static library, and this .lib was then linked to my .exe. To avoid making the analysis unnecessarily complicated, I added the .lib's source code directly to the .exe and re-compiled. The problem persists; but the parameter is now rebased to 0xb000002d instead of 0xbffffa0.

I didn't get the .i file to compile with the /TP switch, so I deleted the original file, renamed the .i to .cpp, added it to the project and rebuilt. I hope that was okay.

The .i file is attached (you find the affected function in line 339934), as well as the .pdb (in case it helps). I had to rename them both to .cpp, otherwise Connect wouldn't accept the uploads. The compiler command line is:
/Zi /nologo /W4 /WX- /MP /Ox /Ob2 /Oi /Oy /GL /D "NDEBUG" /D "CRIC_USE_XNA_MATH" /D "CRIC_BIEDER_CORE_STATIC_BUILD" /GF /Gm- /EHsc /GS- /Gy /arch:SSE2 /fp:precise /Zc:wchar_t /Zc:forScope /GR- /Fp"..\..\..\Intermediate\Bieder\Core\x86S\Core.pch" /Fo"..\..\..\Intermediate\Bieder\Core\x86S\" /Fd"..\..\..\Intermediate\Bieder\Core\x86S\vc100.pdb" /Gd /analyze- /errorReport:queue

The linker command line is:
/OUT:"..\..\..\Compilations\Bieder\x86S\Core.exe" /NOLOGO "Cric.lib" "Bieder.lib" "Direct3D 11.lib" "Technology Preview.lib" "kernel32.lib" "user32.lib" "gdi32.lib" "winspool.lib" "comdlg32.lib" "advapi32.lib" "shell32.lib" "ole32.lib" "oleaut32.lib" "uuid.lib" "odbc32.lib" "odbccp32.lib" /MANIFEST /ManifestFile:"..\..\..\Intermediate\Bieder\Core\x86S\Core.exe.intermediate.manifest" /ALLOWISOLATION /MANIFESTUAC:"level='asInvoker' uiAccess='false'" /DEBUG /PDB:"X:\Projects\Compilations\Bieder\x86S\Core.pdb" /SUBSYSTEM:WINDOWS",6.0" /LARGEADDRESSAWARE /OPT:REF /OPT:ICF /PGD:"X:\Projects\Compilations\Bieder\x86S\Core.pgd" /LTCG /TLBID:1 /DYNAMICBASE /NXCOMPAT /MACHINE:X86 /ERRORREPORT:QUEUE

If you need further information (e.g. the assembler code of the exception handlers used by the function), feel free to ask.
Posted by Ian [MSFT] on 12/14/2010 at 11:29 AM
Thanks for reporting this issue. I'm with the VC++ product team and i'd like to take a deeper look. In order to investigate i'm going to need a bit more information from you.

I'm going to need a "pre-processed" source file for the cpp file that contains the problem.

To generate such a file compile as normal but add the /P switch. For example: "cl.exe /Og foo.cpp" becomes "cl.exe /Og /P foo.cpp". This will output a ".i" file (foo.i for the previous example) that contains the pre-processed source code. You can verify that the problem reproduces with this file by adding the /TP switch and compiling the ".i" file. Using the example above the command to verify the problem would be "cl.exe /Og /TP foo.i".

Once you've verified that your ".i" file generates the incorrect assembly sequence. Please attach the ".i" file and the exact command line you used to compile it.

VC++ Optimizer Team
Posted by Krishty on 12/10/2010 at 7:55 AM
I'm using VS 2010. Just installed the SP1 Beta to make sure I'm up to date, but the problem remains.

I cannot reproduce the problem in a simple test case. I desperately tried, but it seems to be highly dependent on the context (especially of the inline semantics and the .text size of the function's caller). It only appears in x86 with /Og enabled; the x64 version works just fine.

I appended a snippet of the code where the bug shows up and commented it for you, and also the assembler listing of the function. I can also send you the source code of all classes and functions involved here, or further assembler listings, MAP files etc if you need it. I can, however, not hand out the project's complete source code.

I was silently hoping you had a list of known bugs in /Og and you could say, "oh, that's certainly this one, we're already fixing it for the next release", as there are already other bug reports regarding /Og. (E.g. this one: https://connect.microsoft.com/VisualStudio/feedback/details/512552 has to do with access violations, exception handling and /Og, but as it has been fixed in SP1, it appearently didn't have anything to do with my problem :( )
Posted by Microsoft on 12/9/2010 at 10:35 PM
I can't see any code attachment or code snippet in the bug report. I also don't know which compiler version you are reporting this bug on.

If you are using VS2005 or VS2008, then I recommend trying VS2010.

If you are using VS2010, then we will need some form of code sample that reproduces the problem. It's not really possible to diagnose an issue without having some form of code that we can repro the problem with.
Posted by Microsoft on 11/25/2010 at 11:07 PM
Thanks for your feedback.
We are routing this issue to the appropriate group within the Visual Studio Product Team for triage and resolution. These specialized experts will follow-up with your issue.
Posted by Microsoft on 11/25/2010 at 11:21 AM
Thank you for your feedback, we are currently reviewing the issue you have submitted. If this issue is urgent, please contact support directly(http://support.microsoft.com)