1

Topic is C++ exceptions internal representation in WIndows x86 binaries compiled with msvc.

I've found a code that straight up doesn't have nothing (all fields of FuncInfo zero) except the EHFlags set to 5.

Here is the dissasembly:

.text:00407010 ; Attributes: bp-based frame
.text:00407010
.text:00407010 ; int __cdecl std::_Deallocate<8,0>(void *block, unsigned int __formal)
.text:00407010 ??$_Deallocate@$07$0A@@std@@YAXPAXI@Z proc near
.text:00407010                                         ; CODE XREF: std::_Deallocate<8,0>(void *,uint)↑j
.text:00407010
.text:00407010 var_C           = dword ptr -0Ch
.text:00407010 block           = dword ptr  8
.text:00407010 __formal        = dword ptr  0Ch
.text:00407010
.text:00407010 ; FUNCTION CHUNK AT .text:00458370 SIZE 0000001B BYTES
.text:00407010
.text:00407010 ; __unwind { // ??$_Deallocate@$07$0A@@std@@YAXPAXI@Z_SEH
.text:00407010                 push    ebp
.text:00407011                 mov     ebp, esp
.text:00407013                 push    0FFFFFFFFh
.text:00407015                 push    offset ??$_Deallocate@$07$0A@@std@@YAXPAXI@Z_SEH
.text:0040701A                 mov     eax, large fs:0
.text:00407020                 push    eax
.text:00407021                 mov     eax, ___security_cookie
.text:00407026                 xor     eax, ebp
.text:00407028                 push    eax
.text:00407029                 lea     eax, [ebp+var_C]
.text:0040702C                 mov     large fs:0, eax
.text:00407032                 cmp     [ebp+__formal], 1000h
.text:00407039                 jb      short loc_40704B
.text:0040703B                 lea     eax, [ebp+__formal]
.text:0040703E                 push    eax             ; unsigned int *
.text:0040703F                 lea     ecx, [ebp+block]
.text:00407042                 push    ecx             ; void **
.text:00407043                 call    j_?_Adjust_manually_vector_aligned@std@@YAXAAPAXAAI@Z ; std::_Adjust_manually_vector_aligned(void * &,uint &)
.text:00407048                 add     esp, 8
.text:0040704B
.text:0040704B loc_40704B:                             ; CODE XREF: std::_Deallocate<8,0>(void *,uint)+29↑j
.text:0040704B                 mov     edx, [ebp+__formal]
.text:0040704E                 push    edx             ; __formal
.text:0040704F                 mov     eax, [ebp+block]
.text:00407052                 push    eax             ; block
.text:00407053                 call    j_??3@YAXPAXI@Z ; operator delete(void *,uint)
.text:00407058                 add     esp, 8
.text:0040705B                 mov     ecx, [ebp+var_C]
.text:0040705E                 mov     large fs:0, ecx
.text:00407065                 pop     ecx
.text:00407066                 mov     esp, ebp
.text:00407068                 pop     ebp
.text:00407069                 retn
.text:00407069 ; } // starts at 407010
.text:00407069 ??$_Deallocate@$07$0A@@std@@YAXPAXI@Z endp

And the FuncInfo data is:

FuncInfo <19930522h, 0, 0, 0, 0, 0, 0, 0, 5>

Here is the code snippet straight up from CRT source files(xmemory):

//template <size_t _Align>
void _Deallocate(void* _Ptr, size_t _Bytes) noexcept {
    // deallocate storage allocated by _Allocate when !_HAS_ALIGNED_NEW || _Align <= __STDCPP_DEFAULT_NEW_ALIGNMENT__
#if defined(_M_IX86) || defined(_M_X64)
    if (_Bytes >= std::_Big_allocation_threshold) { // boost the alignment of big allocations to help autovectorization
        std::_Adjust_manually_vector_aligned(_Ptr, _Bytes);
    }
#endif // defined(_M_IX86) || defined(_M_X64)

    ::operator delete(_Ptr, _Bytes);
}

I tried tinkering with it and I found that that the main cause for the weird FuncInfo in that case is the call to std::_Adjust_manually_vector_aligned which is defined in the same file as:

// FUNCTION TEMPLATE _Adjust_manually_vector_aligned
inline void _Adjust_manually_vector_aligned(void*& _Ptr, size_t& _Bytes) {
    // adjust parameters from _Allocate_manually_vector_aligned to pass to operator delete
    _Bytes += _Non_user_size;

    const uintptr_t* const _Ptr_user = reinterpret_cast<uintptr_t*>(_Ptr);
    const uintptr_t _Ptr_container   = _Ptr_user[-1];

    // If the following asserts, it likely means that we are performing
    // an aligned delete on memory coming from an unaligned allocation.
    _STL_ASSERT(_Ptr_user[-2] == _Big_allocation_sentinel, "invalid argument");

    // Extra paranoia on aligned allocation/deallocation; ensure _Ptr_container is
    // in range [_Min_back_shift, _Non_user_size]
#ifdef _DEBUG
    constexpr uintptr_t _Min_back_shift = 2 * sizeof(void*);
#else // ^^^ _DEBUG ^^^ // vvv !_DEBUG vvv
    constexpr uintptr_t _Min_back_shift = sizeof(void*);
#endif // _DEBUG
    const uintptr_t _Back_shift = reinterpret_cast<uintptr_t>(_Ptr) - _Ptr_container;
    _STL_VERIFY(_Back_shift >= _Min_back_shift && _Back_shift <= _Non_user_size, "invalid argument");
    _Ptr = reinterpret_cast<void*>(_Ptr_container);
}

This is far as I went into this.

5
  • Also btw I found a case where there is no unwind table either. Mind blown.
    – rec
    Apr 24, 2020 at 20:28
  • If you add it to the question (with code) I’ll reopen it.
    – Igor Skochinsky
    Apr 24, 2020 at 20:29
  • Related asks for zero unwind map. Here we ask for zero unwind and zero tryblockmap.
    – rec
    Apr 25, 2020 at 14:43
  • yes, so I reopened it
    – Igor Skochinsky
    Apr 25, 2020 at 18:11
  • Yes I know but I made the other question appear in the linked tab. So other people can see it too.
    – rec
    Apr 25, 2020 at 18:12

1 Answer 1

3

So, EHFlags bits are defined as follows (see ehdata_values.h):

#define FI_EHS_FLAG             0x00000001
#define FI_DYNSTKALIGN_FLAG     0x00000002
#define FI_EHNOEXCEPT_FLAG      0x00000004

i.e. 5 is FI_EHS_FLAG|FI_EHNOEXCEPT_FLAG

In crt\src\vcruntime\frame.cpp we can find this snippet:

auto tryBlockMap = T::TryBlockMap(pFuncInfo, pDC);
if (tryBlockMap.getNumTryBlocks() != 0
    //
    // If the function has no try block, we still want to call the
    // frame handler if there is an exception specification
    //
    || (T::getMagicNum(pFuncInfo) >= EH_MAGIC_NUMBER2 && (T::getESTypes(pFuncInfo) != nullptr))
    || (T::getMagicNum(pFuncInfo) >= EH_MAGIC_NUMBER3 && (T::isNoExcept(pFuncInfo) != 0)))
{

It seems the comment is a little outdated and should read "if there is an exception specification or noexcept specified".

From cppreference:

Non-throwing functions are permitted to call potentially-throwing functions. Whenever an exception is thrown and the search for a handler encounters the outermost block of a non-throwing function, the function std::terminate is called.

So it seems the otherwise empty FuncInfo is used here as a flag to mark a noexcept function so if an exception does happen after all, the program is properly terminated.

Here's a small sample which produces such FuncInfo:

int f() noexcept
{
    static const int arr[5] = {1,2,3,4,6};
    for (int i = 0; i < sizeof(arr)/sizeof(*arr); i++)
    {
        std::cout << arr[i] << std::endl;
    }

    return 0;
}

int main()
{
 return f();
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.