Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does GetLastError() return 0 or 2 depending on how it is called?

I am using mingw g++ 4.6.1 with -O0, WinXP SP2.

Minimal working example is here.

g++ is configured with --disable-sjlj-exceptions --with-dwarf2.

GetLastError() returns 0 or 2 depeding on how the exception is thrown:

throw runtime_error(error_message());

bogus "error code: 0" is printed, and

const string msg = error_message();

throw runtime_error(msg);

prints "error code: 2" as expected.

First, I thought GetLastError() is invoked twice but debugging shows it is invoked exactly once, as expected.

What is going on?

like image 536
Ali Avatar asked Dec 29 '11 18:12

Ali


2 Answers

It's possible that the code that sets up a throw calls a Win32 API function inside itself somewhere, that resets the Last-Error value to 0. This may be happening before your call to error_message().

Calling GetLastError() does not automatically reset the Last-Error value to 0, so it is safe to call twice.

Whether your compiler/runtime generates code that calls a Win32 API function will be up to your specific runtime. In order to be safe and not depend on this, use the two-statement version:

const string msg = error_message();
throw runtime_error(msg);

Better yet, for future readers of your code it would be useful to call GetLastError() outside error_message():

const string msg = error_message(GetLastError());
throw runtime_error(msg);

This way, readers will see the GetLastError() call immediately after the corresponding Win32 API call, where it belongs.

like image 193
Greg Hewgill Avatar answered Nov 06 '22 03:11

Greg Hewgill


If you look at the assembly code generated, it become clear what's happening. The following C++ code:

hDevice = CreateFileA(path, // drive to open
    // etc...
    );

if (hDevice == INVALID_HANDLE_VALUE) // cannot open the drive  
{                                                              
    throw runtime_error(error_message());
}

Generates a stretch of assembly code (at least using default optimization):

    call    _CreateFileA@28  #
LEHE4:
    sub esp, 28  #,
    mov DWORD PTR [ebp-12], eax  # hDevice, D.51673
    cmp DWORD PTR [ebp-12], -1   # hDevice,
    jne L5   #,
    mov DWORD PTR [esp], 8   #,

    call    ___cxa_allocate_exception    # // <--- this call is made between the 
                                             # //    CreateFile() call and the 
                                             # //    error_message() call

    mov ebx, eax     # D.50764,
    lea eax, [ebp-16]    # tmp66,
    mov DWORD PTR [esp], eax     #, tmp66
LEHB5:
    call    __Z13error_messagev  #

You see a call made to ___cxa_allocate_exception to allocate some memory block for the exception being thrown. That function call is changing the GetLastError() state.

When the C++ code looks like:

hDevice = CreateFileA(path, // drive to open
    // etc...
    );

if (hDevice == INVALID_HANDLE_VALUE) // cannot open the drive  
{                                                              
    const string msg = error_message();

    throw runtime_error(msg);
}

Then you get the following generated assembly:

    call    _CreateFileA@28  #
    sub esp, 28  #,
    mov DWORD PTR [ebp-12], eax  # hDevice, D.51674
    cmp DWORD PTR [ebp-12], -1   # hDevice,
    jne L5   #,
    lea eax, [ebp-16]    # tmp66,
    mov DWORD PTR [esp], eax     #, tmp66
    call    __Z13error_messagev  #
LEHE4:
    sub esp, 4   #,
    mov DWORD PTR [esp], 8   #,

    call    ___cxa_allocate_exception  # // <--- now this happens *after*
                                         //     error_message() has been called

which does not call an external function between the failed CreateFile() call and the call to error_message().

This kind of problem is one of the main problems with error handling using some global state like GetLastError() or errno.

like image 45
Michael Burr Avatar answered Nov 06 '22 04:11

Michael Burr