├── .github └── workflows │ └── main.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── build ├── FindWDK.cmake └── vcrtl_driver.props ├── src ├── algorithm.h ├── assert.cpp ├── assert.h ├── bugcheck.h ├── flags.h ├── intrin.h ├── limits.h ├── memcpy.h ├── ptr_to_member.h ├── runtime.cpp ├── rva.h ├── stddef.h ├── stdint.h ├── type_info.cpp ├── type_info.h ├── type_traits.h ├── utils.h ├── win32_seh.h ├── x64 │ ├── README.md │ ├── capture.asm │ ├── cpu_context.h │ ├── eh_structs_x64.h │ ├── fh3.cpp │ ├── fh4.cpp │ ├── throw.cpp │ ├── throw.h │ └── unwind_handler.cpp └── x86 │ ├── README.md │ ├── eh_helpers.asm │ ├── eh_structs_x86.h │ ├── fh3_x86.asm │ ├── memcpy.asm │ ├── nlg.asm │ └── throw_x86.cpp └── test ├── build.sln ├── build_test.inf ├── build_test.vcxproj ├── build_test.vcxproj.filters └── test.cpp /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build driver libs 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: windows-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Compile x86 13 | run: | 14 | mkdir _build_x86 15 | cd _build_x86 16 | cmake .. -A win32 17 | cmake --build . --config RelWithDebInfo 18 | - name: Compile x64 19 | run: | 20 | mkdir _build_x64 21 | cd _build_x64 22 | cmake .. -A x64 23 | cmake --build . --config RelWithDebInfo 24 | - name: Collect outputs 25 | run: | 26 | mkdir _output 27 | mkdir _output/x86 28 | mkdir _output/x64 29 | cp _build_x86\RelWithDebInfo\vcrtl_driver.lib _output\x86 30 | cp _build_x86\vcrtl_driver.dir\RelWithDebInfo\vcrtl_driver.pdb _output\x86 31 | cp _build_x64\RelWithDebInfo\vcrtl_driver.lib _output\x64 32 | cp _build_x64\vcrtl_driver.dir\RelWithDebInfo\vcrtl_driver.pdb _output\x64 33 | cp build\vcrtl_driver.props _output 34 | - name: Upload x64 35 | uses: actions/upload-artifact@v1 36 | with: 37 | name: output 38 | path: _output 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _*/ 2 | .vs/ 3 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | cmake_policy(SET CMP0091 OLD) 3 | 4 | project(vcrtl CXX ASM_MASM) 5 | 6 | list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/build) 7 | find_package(WDK REQUIRED) 8 | 9 | list(REMOVE_ITEM WDK_COMPILE_FLAGS /kernel) 10 | 11 | wdk_add_library(vcrtl_driver STATIC 12 | src/algorithm.h 13 | src/assert.cpp 14 | src/assert.h 15 | src/bugcheck.h 16 | src/flags.h 17 | src/intrin.h 18 | src/limits.h 19 | src/memcpy.h 20 | src/ptr_to_member.h 21 | src/runtime.cpp 22 | src/rva.h 23 | src/stddef.h 24 | src/stdint.h 25 | src/type_info.cpp 26 | src/type_info.h 27 | src/type_traits.h 28 | src/utils.h 29 | src/win32_seh.h 30 | ) 31 | 32 | if (CMAKE_SIZEOF_VOID_P EQUAL 8) 33 | target_sources(vcrtl_driver PUBLIC 34 | src/x64/capture.asm 35 | src/x64/cpu_context.h 36 | src/x64/eh_structs_x64.h 37 | src/x64/fh3.cpp 38 | src/x64/fh4.cpp 39 | src/x64/throw.cpp 40 | src/x64/throw.h 41 | src/x64/unwind_handler.cpp 42 | ) 43 | else() 44 | target_sources(vcrtl_driver PUBLIC 45 | src/x86/eh_helpers.asm 46 | src/x86/eh_structs_x86.h 47 | src/x86/fh3_x86.asm 48 | src/x86/memcpy.asm 49 | src/x86/nlg.asm 50 | src/x86/throw_x86.cpp 51 | ) 52 | list(APPEND CMAKE_ASM_MASM_FLAGS "/safeseh") 53 | endif() 54 | 55 | target_compile_features(vcrtl_driver PUBLIC cxx_std_17) 56 | 57 | wdk_add_driver(vcrtl_driver_test test/test.cpp) 58 | target_link_libraries(vcrtl_driver_test vcrtl_driver) 59 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy 2 | of this software and associated documentation files (the "Software"), to deal 3 | in the Software without restriction, including without limitation the rights 4 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 5 | copies of the Software, and to permit persons to whom the Software is 6 | furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all 9 | copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 | SOFTWARE. 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # C++ Exceptions in Windows Drivers 2 | 3 | This project implements parts of the Visual Studio runtime library 4 | that are needed for C++ exception handling. Currently, x86 and x64 5 | platforms are supported. 6 | 7 | ## Getting started 8 | 9 | To use exceptions in your kernel-mode driver, first 10 | 11 | * [download the binaries](https://github.com/avakar/vcrtl/releases), and then 12 | * add `vcrtl_driver.props` to your driver project. 13 | 14 | C++ exceptions will magically work. 15 | 16 | ## Features 17 | 18 | The exception handling code was optimized to significantly reduce 19 | the required stack space. On x86, the stack usage is negligible, 20 | on x64, approximately 300 bytes are used during handler search, 21 | these are, however, reclaimed before the catch handler is called. 22 | 23 | No dynamic allocations or thread-local storage is used, everything 24 | happens on the stack. 25 | 26 | On x64, both FH3 and FH4 C++ exception ABI is supported. FH4 is 27 | much better than FH3, prefer it. 28 | 29 | No string comparisons are done during exception dispatch. 30 | 31 | ## Limitations 32 | 33 | An exception must not leave the module in which it was thrown, 34 | otherwise the dispatcher will bug-check. 35 | 36 | No SEH exception may pass through frames in which you do C++ 37 | exception handling (this includes functions with `try/catch` blocks 38 | or functions marked as `noexcept`, or functions with automatic variables 39 | with non-trivial destructors). This will be detected and 40 | you'll get a bug-check. 41 | 42 | ## TODO 43 | 44 | Although `/GS` is supported, the frame cookies aren't checked yet. 45 | -------------------------------------------------------------------------------- /build/FindWDK.cmake: -------------------------------------------------------------------------------- 1 | # Redistribution and use is allowed under the OSI-approved 3-clause BSD license. 2 | # Copyright (c) 2018 Sergey Podobry (sergey.podobry at gmail.com). All rights reserved. 3 | 4 | #.rst: 5 | # FindWDK 6 | # ---------- 7 | # 8 | # This module searches for the installed Windows Development Kit (WDK) and 9 | # exposes commands for creating kernel drivers and kernel libraries. 10 | # 11 | # Output variables: 12 | # - `WDK_FOUND` -- if false, do not try to use WDK 13 | # - `WDK_ROOT` -- where WDK is installed 14 | # - `WDK_VERSION` -- the version of the selected WDK 15 | # - `WDK_WINVER` -- the WINVER used for kernel drivers and libraries 16 | # (default value is `0x0601` and can be changed per target or globally) 17 | # 18 | # Example usage: 19 | # 20 | # find_package(WDK REQUIRED) 21 | # 22 | # wdk_add_library(KmdfCppLib STATIC KMDF 1.15 23 | # KmdfCppLib.h 24 | # KmdfCppLib.cpp 25 | # ) 26 | # target_include_directories(KmdfCppLib INTERFACE .) 27 | # 28 | # wdk_add_driver(KmdfCppDriver KMDF 1.15 29 | # Main.cpp 30 | # ) 31 | # target_link_libraries(KmdfCppDriver KmdfCppLib) 32 | # 33 | 34 | if(DEFINED ENV{WDKContentRoot}) 35 | file(GLOB WDK_NTDDK_FILES 36 | "$ENV{WDKContentRoot}/Include/*/km/ntddk.h" 37 | ) 38 | else() 39 | file(GLOB WDK_NTDDK_FILES 40 | "C:/Program Files*/Windows Kits/10/Include/*/km/ntddk.h" 41 | ) 42 | endif() 43 | 44 | if(WDK_NTDDK_FILES) 45 | list(GET WDK_NTDDK_FILES -1 WDK_LATEST_NTDDK_FILE) 46 | endif() 47 | 48 | include(FindPackageHandleStandardArgs) 49 | find_package_handle_standard_args(WDK REQUIRED_VARS WDK_LATEST_NTDDK_FILE) 50 | 51 | if (NOT WDK_LATEST_NTDDK_FILE) 52 | return() 53 | endif() 54 | 55 | get_filename_component(WDK_ROOT ${WDK_LATEST_NTDDK_FILE} DIRECTORY) 56 | get_filename_component(WDK_ROOT ${WDK_ROOT} DIRECTORY) 57 | get_filename_component(WDK_VERSION ${WDK_ROOT} NAME) 58 | get_filename_component(WDK_ROOT ${WDK_ROOT} DIRECTORY) 59 | get_filename_component(WDK_ROOT ${WDK_ROOT} DIRECTORY) 60 | 61 | message(STATUS "WDK_ROOT: " ${WDK_ROOT}) 62 | message(STATUS "WDK_VERSION: " ${WDK_VERSION}) 63 | 64 | set(WDK_WINVER "0x0601" CACHE STRING "Default WINVER for WDK targets") 65 | 66 | set(WDK_ADDITIONAL_FLAGS_FILE "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/wdkflags.h") 67 | file(WRITE ${WDK_ADDITIONAL_FLAGS_FILE} "#pragma runtime_checks(\"suc\", off)") 68 | 69 | set(WDK_COMPILE_FLAGS 70 | "/Zp8" # set struct alignment 71 | "/GF" # enable string pooling 72 | "/GR-" # disable RTTI 73 | "/Gz" # __stdcall by default 74 | "/kernel" # create kernel mode binary 75 | "/FIwarning.h" # disable warnings in WDK headers 76 | "/FI${WDK_ADDITIONAL_FLAGS_FILE}" # include file to disable RTC 77 | ) 78 | 79 | set(WDK_COMPILE_DEFINITIONS "WINNT=1") 80 | set(WDK_COMPILE_DEFINITIONS_DEBUG "MSC_NOOPT;DEPRECATE_DDK_FUNCTIONS=1;DBG=1") 81 | 82 | if(CMAKE_SIZEOF_VOID_P EQUAL 4) 83 | list(APPEND WDK_COMPILE_DEFINITIONS "_X86_=1;i386=1;STD_CALL") 84 | set(WDK_PLATFORM "x86") 85 | elseif(CMAKE_SIZEOF_VOID_P EQUAL 8) 86 | list(APPEND WDK_COMPILE_DEFINITIONS "_WIN64;_AMD64_;AMD64") 87 | set(WDK_PLATFORM "x64") 88 | else() 89 | message(FATAL_ERROR "Unsupported architecture") 90 | endif() 91 | 92 | string(CONCAT WDK_LINK_FLAGS 93 | "/MANIFEST:NO " # 94 | "/DRIVER " # 95 | "/OPT:REF " # 96 | "/INCREMENTAL:NO " # 97 | "/OPT:ICF " # 98 | "/SUBSYSTEM:NATIVE " # 99 | "/MERGE:_TEXT=.text;_PAGE=PAGE " # 100 | "/NODEFAULTLIB " # do not link default CRT 101 | "/SECTION:INIT,d " # 102 | "/VERSION:10.0 " # 103 | ) 104 | 105 | # Generate imported targets for WDK lib files 106 | file(GLOB WDK_LIBRARIES "${WDK_ROOT}/Lib/${WDK_VERSION}/km/${WDK_PLATFORM}/*.lib") 107 | foreach(LIBRARY IN LISTS WDK_LIBRARIES) 108 | get_filename_component(LIBRARY_NAME ${LIBRARY} NAME_WE) 109 | string(TOUPPER ${LIBRARY_NAME} LIBRARY_NAME) 110 | add_library(WDK::${LIBRARY_NAME} INTERFACE IMPORTED) 111 | set_property(TARGET WDK::${LIBRARY_NAME} PROPERTY INTERFACE_LINK_LIBRARIES ${LIBRARY}) 112 | endforeach(LIBRARY) 113 | unset(WDK_LIBRARIES) 114 | 115 | function(wdk_add_driver _target) 116 | cmake_parse_arguments(WDK "" "KMDF;WINVER" "" ${ARGN}) 117 | 118 | add_executable(${_target} ${WDK_UNPARSED_ARGUMENTS}) 119 | 120 | set_target_properties(${_target} PROPERTIES SUFFIX ".sys") 121 | set_target_properties(${_target} PROPERTIES COMPILE_OPTIONS "${WDK_COMPILE_FLAGS}") 122 | set_target_properties(${_target} PROPERTIES COMPILE_DEFINITIONS 123 | "${WDK_COMPILE_DEFINITIONS};$<$:${WDK_COMPILE_DEFINITIONS_DEBUG}>;_WIN32_WINNT=${WDK_WINVER}" 124 | ) 125 | set_target_properties(${_target} PROPERTIES LINK_FLAGS "${WDK_LINK_FLAGS}") 126 | 127 | target_include_directories(${_target} SYSTEM PRIVATE 128 | "${WDK_ROOT}/Include/${WDK_VERSION}/shared" 129 | "${WDK_ROOT}/Include/${WDK_VERSION}/km" 130 | ) 131 | 132 | target_link_libraries(${_target} WDK::NTOSKRNL WDK::HAL WDK::BUFFEROVERFLOWK WDK::WMILIB) 133 | 134 | if(CMAKE_SIZEOF_VOID_P EQUAL 4) 135 | target_link_libraries(${_target} WDK::MEMCMP) 136 | endif() 137 | 138 | if(DEFINED WDK_KMDF) 139 | target_include_directories(${_target} SYSTEM PRIVATE "${WDK_ROOT}/Include/wdf/kmdf/${WDK_KMDF}") 140 | target_link_libraries(${_target} 141 | "${WDK_ROOT}/Lib/wdf/kmdf/${WDK_PLATFORM}/${WDK_KMDF}/WdfDriverEntry.lib" 142 | "${WDK_ROOT}/Lib/wdf/kmdf/${WDK_PLATFORM}/${WDK_KMDF}/WdfLdr.lib" 143 | ) 144 | 145 | if(CMAKE_SIZEOF_VOID_P EQUAL 4) 146 | set_property(TARGET ${_target} APPEND_STRING PROPERTY LINK_FLAGS "/ENTRY:FxDriverEntry@8") 147 | elseif(CMAKE_SIZEOF_VOID_P EQUAL 8) 148 | set_property(TARGET ${_target} APPEND_STRING PROPERTY LINK_FLAGS "/ENTRY:FxDriverEntry") 149 | endif() 150 | else() 151 | if(CMAKE_SIZEOF_VOID_P EQUAL 4) 152 | set_property(TARGET ${_target} APPEND_STRING PROPERTY LINK_FLAGS "/ENTRY:GsDriverEntry@8") 153 | elseif(CMAKE_SIZEOF_VOID_P EQUAL 8) 154 | set_property(TARGET ${_target} APPEND_STRING PROPERTY LINK_FLAGS "/ENTRY:GsDriverEntry") 155 | endif() 156 | endif() 157 | endfunction() 158 | 159 | function(wdk_add_library _target) 160 | cmake_parse_arguments(WDK "" "KMDF;WINVER" "" ${ARGN}) 161 | 162 | add_library(${_target} ${WDK_UNPARSED_ARGUMENTS}) 163 | 164 | set_target_properties(${_target} PROPERTIES COMPILE_OPTIONS "${WDK_COMPILE_FLAGS}") 165 | set_target_properties(${_target} PROPERTIES COMPILE_DEFINITIONS 166 | "${WDK_COMPILE_DEFINITIONS};$<$:${WDK_COMPILE_DEFINITIONS_DEBUG};_WIN32_WINNT=${WDK_WINVER}>" 167 | ) 168 | 169 | target_include_directories(${_target} SYSTEM PRIVATE 170 | "${WDK_ROOT}/Include/${WDK_VERSION}/shared" 171 | "${WDK_ROOT}/Include/${WDK_VERSION}/km" 172 | ) 173 | 174 | if(DEFINED WDK_KMDF) 175 | target_include_directories(${_target} SYSTEM PRIVATE "${WDK_ROOT}/Include/wdf/kmdf/${WDK_KMDF}") 176 | endif() 177 | endfunction() 178 | -------------------------------------------------------------------------------- /build/vcrtl_driver.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | true 7 | 8 | 9 | 10 | Sync 11 | stdcpp17 12 | 13 | 14 | $(MSBuildThisFileDirectory)x86\vcrtl_driver.lib;%(AdditionalDependencies) 15 | $(MSBuildThisFileDirectory)$(Platform)\vcrtl_driver.lib;%(AdditionalDependencies) 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/algorithm.h: -------------------------------------------------------------------------------- 1 | namespace vcrtl { 2 | 3 | template 4 | bool binary_search(ForwardIt first, ForwardIt last, T const & value) 5 | { 6 | while (first != last) 7 | { 8 | ForwardIt mid = first + (last - first) / 2; 9 | if (value < *mid) 10 | last = mid; 11 | else if (*mid < value) 12 | first = ++mid; 13 | else 14 | return true; 15 | } 16 | 17 | return false; 18 | } 19 | 20 | } 21 | 22 | #pragma once 23 | -------------------------------------------------------------------------------- /src/assert.cpp: -------------------------------------------------------------------------------- 1 | #include "assert.h" 2 | #include "bugcheck.h" 3 | 4 | #include 5 | 6 | void vcrtl::_verify(bool cond) 7 | { 8 | if (!cond) 9 | on_bug_check(bug_check_reason::assertion_failure); 10 | } 11 | 12 | void vcrtl::on_bug_check(bug_check_reason reason) 13 | { 14 | KeBugCheckEx(DRIVER_VIOLATION, (ULONG_PTR)reason, 0, 0, 0); 15 | } 16 | -------------------------------------------------------------------------------- /src/assert.h: -------------------------------------------------------------------------------- 1 | namespace vcrtl { 2 | 3 | void _verify(bool cond); 4 | 5 | template 6 | void verify(E && cond) 7 | { 8 | _verify(static_cast(cond)); 9 | } 10 | 11 | } 12 | 13 | #pragma once 14 | -------------------------------------------------------------------------------- /src/bugcheck.h: -------------------------------------------------------------------------------- 1 | namespace vcrtl { 2 | 3 | enum class bug_check_reason 4 | { 5 | unwind_on_unsafe_exception, 6 | invalid_target_unwind, 7 | corrupted_function_unwind_state, 8 | corrupted_eh_unwind_data, 9 | corrupted_exception_state, 10 | 11 | unwinding_non_cxx_frame, 12 | 13 | seh_handler_not_in_safeseh, 14 | destructor_threw_during_unwind, 15 | corrupted_exception_registration_chain, 16 | noexcept_violation, 17 | exception_specification_not_supported, 18 | no_matching_exception_handler, 19 | 20 | assertion_failure, 21 | forbidden_call, 22 | std_terminate, 23 | }; 24 | 25 | [[noreturn]] void on_bug_check(bug_check_reason reason); 26 | 27 | } 28 | 29 | #pragma once 30 | -------------------------------------------------------------------------------- /src/flags.h: -------------------------------------------------------------------------------- 1 | #include "type_traits.h" 2 | 3 | namespace vcrtl { 4 | 5 | struct nullflag_t 6 | { 7 | }; 8 | 9 | constexpr nullflag_t nullflag = {}; 10 | 11 | template 12 | struct flags 13 | { 14 | using type = underlying_type_t; 15 | 16 | flags() noexcept = default; 17 | 18 | flags(nullflag_t) noexcept 19 | : _value(0) 20 | { 21 | } 22 | 23 | flags(E o) noexcept 24 | : _value(static_cast(o)) 25 | { 26 | } 27 | 28 | explicit flags(type o) noexcept 29 | : _value(o) 30 | { 31 | } 32 | 33 | operator type() const 34 | { 35 | return _value; 36 | } 37 | 38 | explicit operator bool() const 39 | { 40 | return _value != 0; 41 | } 42 | 43 | bool has_any_of(flags f) const 44 | { 45 | return (_value & f._value) != 0; 46 | } 47 | 48 | template 49 | type get() const 50 | { 51 | static constexpr type mask = static_cast(e); 52 | static constexpr size_t leading_zeros = _ctz(mask); 53 | 54 | if constexpr (((mask - 1) & mask) != 0) 55 | { 56 | return (_value & mask) >> leading_zeros; 57 | } 58 | else 59 | { 60 | return (_value & mask) != 0; 61 | } 62 | } 63 | 64 | friend flags operator|(E l, E r) 65 | { 66 | flags f; 67 | f._value = static_cast(l) | static_cast(r); 68 | return f; 69 | } 70 | 71 | friend flags operator&(flags l, E r) 72 | { 73 | flags f; 74 | f._value = l._value & static_cast(r); 75 | return f; 76 | } 77 | 78 | private: 79 | static constexpr size_t _ctz(type val) 80 | { 81 | size_t r = 0; 82 | while ((val & 1) == 0) 83 | { 84 | ++r; 85 | val >>= 1; 86 | } 87 | 88 | return r; 89 | } 90 | 91 | type _value; 92 | }; 93 | 94 | } 95 | 96 | #pragma once 97 | -------------------------------------------------------------------------------- /src/intrin.h: -------------------------------------------------------------------------------- 1 | #include "stdint.h" 2 | 3 | template 4 | void * __GetExceptionInfo(T); 5 | 6 | extern "C" long _InterlockedIncrement(long * addend); 7 | #pragma intrinsic(_InterlockedIncrement) 8 | 9 | extern "C" short _InterlockedIncrement16(short * addend); 10 | #pragma intrinsic(_InterlockedIncrement16) 11 | 12 | extern "C" long _InterlockedAdd(long volatile * addend, long value); 13 | 14 | extern "C" char _InterlockedExchangeAdd8(char volatile * addend, char value); 15 | #pragma intrinsic(_InterlockedExchangeAdd8) 16 | 17 | extern "C" short _InterlockedExchangeAdd16(short volatile * addend, short value); 18 | #pragma intrinsic(_InterlockedExchangeAdd16) 19 | 20 | extern "C" long _InterlockedExchangeAdd(long volatile * addend, long value); 21 | #pragma intrinsic(_InterlockedExchangeAdd) 22 | 23 | extern "C" void * _AddressOfReturnAddress(); 24 | #pragma intrinsic(_AddressOfReturnAddress) 25 | 26 | #ifdef _M_IX86 27 | 28 | extern "C" unsigned long __readfsdword(unsigned long offset); 29 | #pragma intrinsic(__readfsdword) 30 | 31 | extern "C" void __writefsdword(unsigned long offset, unsigned long value); 32 | #pragma intrinsic(__writefsdword) 33 | 34 | #elif defined(_M_AMD64) 35 | 36 | extern "C" __int64 _InterlockedIncrement64(__int64 * addend); 37 | #pragma intrinsic(_InterlockedIncrement64) 38 | 39 | extern "C" __int64 _InterlockedAdd64(__int64 volatile * addend, __int64 value); 40 | 41 | extern "C" __int64 _InterlockedExchangeAdd64(__int64 volatile * addend, __int64 value); 42 | #pragma intrinsic(_InterlockedExchangeAdd64) 43 | 44 | #endif 45 | #pragma once 46 | -------------------------------------------------------------------------------- /src/limits.h: -------------------------------------------------------------------------------- 1 | namespace vcrtl { 2 | 3 | enum float_round_style 4 | { 5 | round_indeterminate = -1, 6 | round_toward_zero = 0, 7 | round_to_nearest = 1, 8 | round_toward_infinity = 2, 9 | round_toward_neg_infinity = 3 10 | }; 11 | 12 | template 13 | struct numeric_limits 14 | { 15 | static constexpr bool is_specialized = false; 16 | static constexpr bool is_signed = false; 17 | static constexpr bool is_integer = false; 18 | static constexpr bool is_exact = false; 19 | static constexpr bool has_infinity = false; 20 | static constexpr bool has_quiet_NaN = false; 21 | static constexpr bool has_signaling_NaN = false; 22 | static constexpr bool has_denorm = false; 23 | static constexpr bool has_denorm_loss = false; 24 | static constexpr float_round_style round_style = round_toward_zero; 25 | static constexpr bool is_iec599 = false; 26 | static constexpr bool is_bounded = false; 27 | static constexpr bool is_modulo = false; 28 | static constexpr int digits = 0; 29 | static constexpr int digits10 = 0; 30 | static constexpr int max_digits10 = 0; 31 | static constexpr int radix = 0; 32 | static constexpr int min_exponent = 0; 33 | static constexpr int min_exponent10 = 0; 34 | static constexpr int max_exponent = 0; 35 | static constexpr bool traps = false; 36 | static constexpr bool tinyness_before = false; 37 | 38 | static constexpr T min() noexcept { return T(); }; 39 | static constexpr T lowest() noexcept { return T(); }; 40 | static constexpr T max() noexcept { return T(); }; 41 | static constexpr T epsilon() noexcept { return T(); }; 42 | static constexpr T round_error() noexcept { return T(); }; 43 | static constexpr T infinity() noexcept { return T(); }; 44 | static constexpr T quiet_NaN() noexcept { return T(); }; 45 | static constexpr T signaling_NaN() noexcept { return T(); }; 46 | static constexpr T denorm_min() noexcept { return T(); }; 47 | }; 48 | 49 | template 50 | struct numeric_limits 51 | : numeric_limits 52 | { 53 | }; 54 | 55 | template 56 | struct numeric_limits 57 | : numeric_limits 58 | { 59 | }; 60 | 61 | template 62 | struct numeric_limits 63 | : numeric_limits 64 | { 65 | }; 66 | 67 | template 68 | struct _integer_numeric_limits 69 | { 70 | static constexpr bool is_specialized = true; 71 | static constexpr bool is_signed = Min < 0; 72 | static constexpr bool is_integer = true; 73 | static constexpr bool is_exact = true; 74 | static constexpr bool has_infinity = false; 75 | static constexpr bool has_quiet_NaN = false; 76 | static constexpr bool has_signaling_NaN = false; 77 | static constexpr bool has_denorm = false; 78 | static constexpr bool has_denorm_loss = false; 79 | static constexpr float_round_style round_style = round_toward_zero; 80 | static constexpr bool is_iec599 = false; 81 | static constexpr bool is_bounded = true; 82 | static constexpr bool is_modulo = Min >= 0; 83 | static constexpr int digits = Digits; 84 | static constexpr int digits10 = (Digits * 19728) >> 16; 85 | static constexpr int max_digits10 = 0; 86 | static constexpr int radix = 2; 87 | static constexpr int min_exponent = 0; 88 | static constexpr int min_exponent10 = 0; 89 | static constexpr int max_exponent = 0; 90 | static constexpr bool traps = true; 91 | static constexpr bool tinyness_before = false; 92 | 93 | static constexpr T min() noexcept { return Min; }; 94 | static constexpr T lowest() noexcept { return Min; }; 95 | static constexpr T max() noexcept { return Max; }; 96 | static constexpr T epsilon() noexcept { return 0; }; 97 | static constexpr T round_error() noexcept { return 0; }; 98 | static constexpr T infinity() noexcept { return 0; }; 99 | static constexpr T quiet_NaN() noexcept { return 0; }; 100 | static constexpr T signaling_NaN() noexcept { return 0; }; 101 | static constexpr T denorm_min() noexcept { return 0; }; 102 | }; 103 | 104 | template <> 105 | struct numeric_limits 106 | : _integer_numeric_limits 107 | { 108 | }; 109 | 110 | } 111 | 112 | #pragma once 113 | -------------------------------------------------------------------------------- /src/memcpy.h: -------------------------------------------------------------------------------- 1 | #include "stddef.h" 2 | 3 | extern "C" void * memcpy(void * dest, void const * src, size_t size); 4 | #pragma intrinsic(memcpy) 5 | 6 | #pragma once 7 | -------------------------------------------------------------------------------- /src/ptr_to_member.h: -------------------------------------------------------------------------------- 1 | #include "stdint.h" 2 | 3 | namespace vcrtl { 4 | 5 | struct cxx_ptr_to_member 6 | { 7 | int32_t member_offset; 8 | int32_t vbtable_ptr_offset; 9 | int32_t vbase_offset; 10 | 11 | uintptr_t apply(uintptr_t obj) const 12 | { 13 | if (vbtable_ptr_offset >= 0) 14 | { 15 | uintptr_t vbtable_ptr = obj + vbtable_ptr_offset; 16 | uintptr_t vbtable = *(uintptr_t const *)vbtable_ptr; 17 | obj = vbtable_ptr + *(int32_t const *)(vbtable + vbase_offset); 18 | } 19 | return obj + member_offset; 20 | } 21 | }; 22 | 23 | } 24 | 25 | #pragma once 26 | -------------------------------------------------------------------------------- /src/runtime.cpp: -------------------------------------------------------------------------------- 1 | #include "bugcheck.h" 2 | #include "stddef.h" 3 | using namespace vcrtl; 4 | 5 | void __cdecl operator delete(void *, size_t) 6 | { 7 | on_bug_check(bug_check_reason::forbidden_call); 8 | } 9 | 10 | extern "C" void __cdecl __std_terminate() 11 | { 12 | on_bug_check(bug_check_reason::std_terminate); 13 | } 14 | -------------------------------------------------------------------------------- /src/rva.h: -------------------------------------------------------------------------------- 1 | #include "assert.h" 2 | #include "limits.h" 3 | #include "stdint.h" 4 | #include "type_traits.h" 5 | 6 | namespace vcrtl { 7 | 8 | template 9 | constexpr T narrow(U value) noexcept 10 | { 11 | verify(numeric_limits::min() <= value); 12 | verify(value <= numeric_limits::max()); 13 | return static_cast(value); 14 | } 15 | 16 | template 17 | struct rva 18 | { 19 | rva(nullptr_t = nullptr) 20 | : _offset(0) 21 | { 22 | } 23 | 24 | static rva from_displacement(uint32_t disp) 25 | { 26 | return rva(disp); 27 | } 28 | 29 | static rva make(T * ptr, void const * base) 30 | { 31 | return rva(narrow(reinterpret_cast(ptr) - reinterpret_cast(base))); 32 | } 33 | 34 | uint32_t value() const 35 | { 36 | return _offset; 37 | } 38 | 39 | explicit operator bool() const 40 | { 41 | return _offset != 0; 42 | } 43 | 44 | template < 45 | typename U, 46 | enable_if_t, int> = 0 47 | > 48 | operator rva() const 49 | { 50 | return rva::from_displacement(_offset); 51 | } 52 | 53 | friend T * operator+(void const * base, rva rva) 54 | { 55 | return reinterpret_cast(reinterpret_cast(base) + rva._offset); 56 | } 57 | 58 | friend byte const * operator-(T * p, rva r) 59 | { 60 | return reinterpret_cast(reinterpret_cast(p) - r._offset); 61 | } 62 | 63 | friend rva operator+(rva lhs, uint32_t rhs) 64 | { 65 | return rva(lhs._offset + rhs); 66 | } 67 | 68 | rva & operator+=(uint32_t rhs) 69 | { 70 | _offset += rhs; 71 | return *this; 72 | } 73 | 74 | friend bool operator==(rva const & lhs, rva const & rhs) 75 | { 76 | return lhs._offset == rhs._offset; 77 | } 78 | 79 | friend bool operator!=(rva const & lhs, rva const & rhs) 80 | { 81 | return !(lhs == rhs); 82 | } 83 | 84 | friend bool operator<(rva const & lhs, rva const & rhs) 85 | { 86 | return lhs._offset < rhs._offset; 87 | } 88 | 89 | friend bool operator<=(rva const & lhs, rva const & rhs) 90 | { 91 | return !(rhs < lhs); 92 | } 93 | 94 | friend bool operator>(rva const & lhs, rva const & rhs) 95 | { 96 | return rhs < lhs; 97 | } 98 | 99 | friend bool operator>=(rva const & lhs, rva const & rhs) 100 | { 101 | return !(lhs < rhs); 102 | } 103 | 104 | private: 105 | explicit rva(uint32_t offset) 106 | : _offset(offset) 107 | { 108 | } 109 | 110 | uint32_t _offset; 111 | }; 112 | 113 | template 114 | rva make_rva(T * ptr, void const * base) 115 | { 116 | return rva::make(ptr, base); 117 | } 118 | 119 | struct symbol 120 | { 121 | operator byte const *() const 122 | { 123 | return reinterpret_cast(this); 124 | } 125 | 126 | operator uintptr_t() const 127 | { 128 | return reinterpret_cast(this); 129 | } 130 | }; 131 | 132 | } 133 | 134 | #pragma once 135 | -------------------------------------------------------------------------------- /src/stddef.h: -------------------------------------------------------------------------------- 1 | // `size_t` is predefined 2 | 3 | namespace vcrtl { 4 | 5 | using size_t = ::size_t; 6 | using nullptr_t = decltype(nullptr); 7 | 8 | enum class byte: unsigned char {}; 9 | 10 | #if __INTELLISENSE__ 11 | #define offsetof(type, member) ((size_t)&((type *)0)->member) 12 | #else 13 | #define offsetof(type, member) __builtin_offsetof(type, member) 14 | #endif 15 | 16 | #define container_of(ptr, type, member) reinterpret_cast((uintptr_t)(ptr) - offsetof(type, member)) 17 | 18 | } 19 | 20 | #pragma once 21 | -------------------------------------------------------------------------------- /src/stdint.h: -------------------------------------------------------------------------------- 1 | namespace vcrtl { 2 | 3 | using int8_t = signed char; 4 | using int16_t = signed short; 5 | using int32_t = signed int; 6 | using int64_t = signed long long; 7 | 8 | using uint8_t = unsigned char; 9 | using uint16_t = unsigned short; 10 | using uint32_t = unsigned int; 11 | using uint64_t = unsigned long long; 12 | 13 | #ifdef _M_AMD64 14 | 15 | using intptr_t = int64_t; 16 | using uintptr_t = uint64_t; 17 | 18 | #elif defined(_M_IX86) 19 | 20 | using intptr_t = int32_t; 21 | using uintptr_t = uint32_t; 22 | 23 | #else 24 | #error Unknown platform 25 | #endif 26 | 27 | } 28 | 29 | #pragma once 30 | -------------------------------------------------------------------------------- /src/type_info.cpp: -------------------------------------------------------------------------------- 1 | #include "type_info.h" 2 | 3 | type_info::~type_info() 4 | { 5 | } 6 | -------------------------------------------------------------------------------- /src/type_info.h: -------------------------------------------------------------------------------- 1 | // The type of the `typeid` expression is actually 2 | // `const ::type_info` rather than `const std::type_info` 3 | // likely for historical reasons. 4 | // 5 | // For non-polymorphic types, `typeid` simply returns 6 | // a reference to a static `type_info` object for that type. 7 | // For polymorphic types, the function `__RTtypeid` 8 | // is called with the pointer to the object. The functiuon 9 | // returns pointer to the `type_info` for the most-derived 10 | // object. 11 | // 12 | // The objects the compiler produces have the following 13 | // layout. 14 | // 15 | // void * vtable = &`??_7type_info@@6B@`; 16 | // void * undecorated_name = nullptr; 17 | // char decorated_name[] = ...; 18 | // 19 | // Sadly, the reference to type_info's vtable means that we must 20 | // make the struct polymorphic, even though we don't actually 21 | // need it to be. Without the virtual function, the linker 22 | // will complain about a missing symbol. 23 | // We should, however, consider defining the vtable symbol 24 | // to absolute zero to remove the virtual functions. 25 | // We'll still be left with a useless field unfortunately. 26 | // 27 | // The `undecorated_name` field is initialized to zero 28 | // and is used by CRT to cache the string it returns from 29 | // `type_info::name()`. This is the reason the `type_info` 30 | // objects are put into a *read-write* section `.data$r`. 31 | // 32 | // The `decorated_name` is the string CRT returns 33 | // from `type_info::raw_name()`. We'll be returning it from 34 | // `type_info::name()`, although as far as I'm concerned, 35 | // the string is a waste of space too. 36 | // All we really care about is a unique address. 37 | 38 | class type_info final 39 | { 40 | virtual ~type_info(); 41 | 42 | void * _undecorated_name; 43 | char const decorated_name[1]; 44 | }; 45 | 46 | namespace vcrtl { 47 | 48 | using type_info = ::type_info; 49 | 50 | } 51 | 52 | #pragma once 53 | -------------------------------------------------------------------------------- /src/type_traits.h: -------------------------------------------------------------------------------- 1 | namespace vcrtl { 2 | 3 | template 4 | struct enable_if 5 | { 6 | }; 7 | 8 | template 9 | struct enable_if 10 | { 11 | using type = T; 12 | }; 13 | 14 | template 15 | using enable_if_t = typename enable_if<_cond, T>::type; 16 | 17 | template 18 | struct integral_constant 19 | { 20 | using value_type = T; 21 | using type = integral_constant; 22 | static constexpr T value = _value; 23 | 24 | constexpr operator value_type() const noexcept 25 | { 26 | return _value; 27 | } 28 | 29 | constexpr value_type operator()() const noexcept 30 | { 31 | return _value; 32 | } 33 | }; 34 | 35 | template 36 | using bool_constant = integral_constant; 37 | 38 | using false_type = bool_constant; 39 | using true_type = bool_constant; 40 | 41 | template 42 | struct is_integral 43 | : false_type 44 | { 45 | }; 46 | 47 | template 48 | inline constexpr bool is_integral_v = is_integral::value; 49 | 50 | template <> struct is_integral : true_type {}; 51 | template <> struct is_integral : true_type {}; 52 | template <> struct is_integral : true_type {}; 53 | template <> struct is_integral : true_type {}; 54 | template <> struct is_integral : true_type {}; 55 | template <> struct is_integral : true_type {}; 56 | template <> struct is_integral : true_type {}; 57 | template <> struct is_integral : true_type {}; 58 | template <> struct is_integral : true_type {}; 59 | template <> struct is_integral : true_type {}; 60 | template <> struct is_integral : true_type {}; 61 | template <> struct is_integral : true_type {}; 62 | template <> struct is_integral : true_type {}; 63 | template <> struct is_integral : true_type {}; 64 | 65 | #if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED) 66 | template <> struct is_integral : true_type {}; 67 | #endif 68 | 69 | #ifdef __cpp_char8_t 70 | template <> struct is_integral: true_type {}; 71 | #endif 72 | 73 | template 74 | struct remove_extent 75 | { 76 | using type = T; 77 | }; 78 | 79 | template 80 | struct remove_extent 81 | { 82 | using type = T; 83 | }; 84 | 85 | template 86 | using remove_extent_t = typename remove_extent::type; 87 | 88 | template 89 | struct remove_reference 90 | { 91 | using type = T; 92 | }; 93 | 94 | template 95 | struct remove_reference 96 | { 97 | using type = T; 98 | }; 99 | 100 | template 101 | struct remove_reference 102 | { 103 | using type = T; 104 | }; 105 | 106 | template 107 | using remove_reference_t = typename remove_reference::type; 108 | 109 | template 110 | struct is_convertible 111 | : bool_constant<__is_convertible_to(From, To)> 112 | { 113 | }; 114 | 115 | template 116 | inline constexpr bool is_convertible_v = is_convertible::value; 117 | 118 | template 119 | constexpr bool is_trivially_constructible_v = __is_trivially_constructible(T); 120 | 121 | template 122 | constexpr bool is_trivially_destructible_v = __is_trivially_constructible(T); 123 | 124 | template 125 | struct underlying_type 126 | { 127 | using type = __underlying_type(T); 128 | }; 129 | 130 | template 131 | using underlying_type_t = typename underlying_type::type; 132 | 133 | template 134 | struct remove_const 135 | { 136 | using type = T; 137 | }; 138 | 139 | template 140 | struct remove_const 141 | { 142 | using type = T; 143 | }; 144 | 145 | template 146 | using remove_const_t = typename remove_const::type; 147 | 148 | template 149 | struct remove_volatile 150 | { 151 | using type = T; 152 | }; 153 | 154 | template 155 | struct remove_volatile 156 | { 157 | using type = T; 158 | }; 159 | 160 | template 161 | using remove_volatile_t = typename remove_volatile::type; 162 | 163 | template 164 | using remove_cv_t = remove_const_t>; 165 | 166 | template 167 | struct remove_cv 168 | { 169 | using type = remove_cv_t; 170 | }; 171 | 172 | template 173 | struct is_same 174 | : bool_constant 175 | { 176 | }; 177 | 178 | template 179 | struct is_same 180 | : bool_constant 181 | { 182 | }; 183 | 184 | template 185 | struct is_void 186 | : is_same, void> 187 | { 188 | }; 189 | 190 | template 191 | constexpr bool is_same_v = is_void::value; 192 | 193 | } 194 | 195 | #pragma once 196 | -------------------------------------------------------------------------------- /src/utils.h: -------------------------------------------------------------------------------- 1 | #include "stdint.h" 2 | #include "type_traits.h" 3 | 4 | namespace vcrtl { 5 | 6 | template 7 | struct bytes 8 | { 9 | template 10 | static constexpr bool can_store_v 11 | = is_trivially_destructible_v 12 | && is_trivially_constructible_v 13 | && sizeof(U) <= sizeof(T) 14 | && alignof(U) <= alignof(T); 15 | 16 | void * get() 17 | { 18 | return &_storage; 19 | } 20 | 21 | template 22 | enable_if_t, U &> as() 23 | { 24 | return reinterpret_cast(_storage); 25 | } 26 | 27 | template 28 | enable_if_t, U const &> as() const 29 | { 30 | return reinterpret_cast(_storage); 31 | } 32 | 33 | private: 34 | T _storage; 35 | }; 36 | 37 | } 38 | 39 | #pragma once 40 | -------------------------------------------------------------------------------- /src/win32_seh.h: -------------------------------------------------------------------------------- 1 | #include "flags.h" 2 | #include "stddef.h" 3 | #include "stdint.h" 4 | 5 | namespace vcrtl::_msvc { 6 | 7 | enum class win32_exception_flag: uint32_t 8 | { 9 | non_continuable = 0x01, 10 | unwinding = 0x02, 11 | exit_unwind = 0x04, 12 | stack_invalid = 0x08, 13 | nested_call = 0x10, 14 | target_unwind = 0x20, 15 | collided_unwind = 0x40, 16 | }; 17 | 18 | struct win32_exception_record 19 | { 20 | uint32_t code; 21 | flags flags; 22 | win32_exception_record * next; 23 | byte const * address; 24 | uint32_t parameter_count; 25 | }; 26 | 27 | enum class win32_exception_disposition: uint32_t 28 | { 29 | continue_execution = 0, 30 | continue_search = 1, 31 | nested = 2, 32 | collided = 3, 33 | cxx_handler = 0x154d3c64, 34 | }; 35 | 36 | struct x86_cpu_context; 37 | struct x64_cpu_context; 38 | 39 | struct x86_seh_registration; 40 | 41 | using x86_frame_handler_t = win32_exception_disposition __cdecl( 42 | win32_exception_record * exception_record, x86_seh_registration * registration, 43 | x86_cpu_context * cpu_context, void * dispatcher_context); 44 | 45 | struct x86_seh_registration 46 | { 47 | x86_seh_registration * next; 48 | x86_frame_handler_t * handler; 49 | }; 50 | 51 | using x64_frame_handler_t = win32_exception_disposition(win32_exception_record * exception_record, 52 | byte * frame_ptr, x64_cpu_context *, void * dispatcher_context); 53 | 54 | } 55 | 56 | #pragma once 57 | -------------------------------------------------------------------------------- /src/x64/README.md: -------------------------------------------------------------------------------- 1 | # C++ Exception Handling on AMD64 2 | 3 | Here we implement `__CxxThrowException`, a call to which is 4 | emitted by MSVC when a `throw` expression is evaluated. 5 | 6 | ## Overview 7 | 8 | The function 9 | receives two arguments, a pointer to the exception object and a pointer 10 | to a *throw info*, a read-only structure describing the type of the thrown 11 | exception. The function is expected not to return, instead, control 12 | is transferred to the appropriate exception handler. 13 | 14 | [[noreturn]] 15 | void __CxxThrowException(void *, __s_ThrowInfo const *); 16 | 17 | On x64, most function frames on the call stack have an associated entry 18 | in the `.pdata` and `.xdata` sections, which contains enough information 19 | to find the function's caller and may contain a pointer to a frame handler 20 | routine and arbitrary data available to the routine. 21 | 22 | In general, functions which require the destruction of local variables, 23 | contain `try` statements, or are marked as `noexcept` will have a frame 24 | handler. Depending on the settings of the `/d2FH4` flag, the Microsoft's 25 | compiler will use either 26 | 27 | * `__CxxFrameHandler3`, if the flag is disabled, or 28 | * `__CxxFrameHandler4`, otherwise. 29 | 30 | Furthermore, when `/GS` is in effect, function deemed worthy of a security 31 | cookie will be associate to 32 | 33 | * `__GSHandlerCheck_EH`, or 34 | * `__GSHandlerCheck_EH4`. 35 | 36 | Both of the above functions will perform a GS check and then delegate 37 | the work to the non-GS variant. When a function doesn't need 38 | exception handling, but has a security cookie, the compiler will use 39 | `__GSHandlerCheck`. 40 | 41 | ## Exception Objects 42 | 43 | Recall that C++ exceptions can be caught by reference and then rethrown 44 | using the nullary `throw` expression. When that happens and the rethrown 45 | exception is caught again by reference, the C++ standard currently mandates 46 | that the address of the exception object not change. 47 | 48 | We generally want the exception objects to be stack-allocated for performance 49 | reasons. However, once thrown, the stack space cannot be reclaimed until 50 | the exception object -- which as explained above can't move -- is destroyed. 51 | Therefore, the catch handler can't really execute in the frame of it's 52 | containing function. Instead, the msvc compiler will emit a separate function 53 | for each catch handler. When a catch handler is active, the call stack 54 | might look like this. 55 | 56 | /--------------------\ 57 | | Catch handler in F | 58 | +--------------------+ 59 | | | 60 | | Zero or more | 61 | | unwound frames, | 62 | | one of which | 63 | | contains the live | 64 | | exception object | 65 | | | 66 | +--------------------+ 67 | | F | 68 | +--------------------+ 69 | | caller of F | 70 | 71 | To avoid confusion, we use the term *funclet* to refer to the piece of code 72 | that can be called and creates its own stack frame. We use the term 73 | *function* to refer to C++ functions. 74 | 75 | When a function is compiled, one *primary funclet* and zero or more 76 | *catch funclets* are generated for it. Since code in a catch handler 77 | can access variables outside its scope, the compiler will keep all 78 | variables in the primary funclet's frame, with catch funclets only 79 | maintaining a pointer to it. The pointer is passed to the catch funclet 80 | in `rdx`, which would suggest that `rcx` contains an argument too, however, 81 | it doesn't seem to be used. Microsoft's implementation passes the pointer 82 | to the funclet itself in `rcx`. 83 | 84 | The catch variable (i.e. the `e` in `catch (type e)`) also lives in 85 | the primary frame. 86 | 87 | ## The Microsoft's Implementation 88 | 89 | The implementation of `__CxxThrowExpression` in the Microsoft's CRT will 90 | simply call `RaiseException`, which walks the stack and calls the associated 91 | frame handlers. The `EXCEPTION_UNWINDING` flag passed to the routine 92 | is unset, which informs the routines to not perform variable destruction 93 | and to only search for `catch` handlers. If one is not found, the stack walk 94 | continues. 95 | 96 | When a matching catch handler is found, the frame handler calls 97 | `RtlUnwindEx`, which starts the stack walk again, this time with the 98 | `EXCEPTION_UNWIND` set, indicating to the frame handlers to destroy local 99 | variables. 100 | 101 | This approach is compatible with the Windows' SEH handling: `__try/__except` 102 | statements will see C++ exceptions as they pass and can even handle them. 103 | Similarly, `catch (...)` block can catch a SEH exception (an access 104 | violation, for example), if the compiler is instructed to do so. 105 | 106 | There are downsides however. 107 | 108 | During the stack walk, the frame handlers can't pass any data to their 109 | caller's frame handlers. Since C++ functions can be split into multiple 110 | funclets, some things are unnecessarily computed multiple times. 111 | Furthermore, a funclet containing a `try` statement decides to 112 | handle the exception, it could get partially unwound (everything in the 113 | handling `try` block must be destroyed). If another exception is then 114 | thrown from the catch handler, the unwound part of the frame must not be 115 | unwound again. Various tricks are used to communicate whether and which 116 | catch handler was active to enable the frame to unwind correctly. This 117 | includes a reserved 8-byte slot in each funclet's frame and thread-local 118 | variables (ugh). 119 | 120 | Additionally, since the exception handling is performed in two phases (handler 121 | search, then unwind), the frame handlers have to compute some things twice. 122 | FH4 in particular requires a some amount of decompressing and would be 123 | more performant if the search and unwind was done in a single pass. 124 | 125 | Finally, the SEH mechanism allows the execution to resume at the point, 126 | where the exception was thrown. This is used for transparent stack enlargement, 127 | for example. However, since a function can throw and then be resumed 128 | at any point, the whole CPU context must be captured and then restored. 129 | C++ exceptions can't resume and non-volatile context is sufficient. 130 | 131 | Note that t he `CONTEXT` structure is *huge*. On AMD64 with a hypervisor 132 | running, it may even have variable-sized extension fields that require 133 | the use of `_alloca`. Several copies of `CONTEXT` are present on the stack 134 | during the unwind. When the catch handler is eventually executed, the stack 135 | space used by the `CONTEXT` structures is not reclaimed, it becomes 136 | part of the consolidated frame between the funclet and its catch handler. 137 | 138 | As such, each catch handler on the stack can easily take 8kB of space. 139 | This makes the default implementation unsuitable for use in kernel mode, 140 | where stack space is precious. 141 | 142 | ## Our implementation 143 | 144 | We perform the exception handling ourselves, bypassing SEH, with particular 145 | emphasis on low stack usage. Only one copy of the context is kept 146 | during unwinding, and only its non-volatile part. Once the handler is found, 147 | the context is freed from stack. 148 | 149 | The unwinding is performed in a single pass. No thread-local storage is used. 150 | 151 | The behavior differs slightly from Microsoft's. 152 | 153 | * We do not support throwing across module boundaries. This makes type 154 | matching a bit faster, since we don't have to compare class names. 155 | It also makes the unwind faster, as we don't have to lookup image 156 | base address for each function on the stack. If a function from another 157 | image is encountered, our implementation bug-checks. 158 | * There is no interaction with SEH. You can't catch a C++ exception 159 | by a `__try/__except` statement, nor can you catch a SEH exception by 160 | `catch (...)`. We bug-check if a C++ exception passes through a `__try` 161 | statement or if SEH passes through a function with a C++ frame handler. 162 | * The debugger will not see the exception raised. No logs will be emitted 163 | to the output window. 164 | 165 | Additionally, we violate the C++ standard in that we don't call 166 | `std::terminate` if an exception is caught by value and the copy constructor 167 | throws. Instead, we let the exception propagate. This means that the following 168 | two pieces of code are equivalent in our implementation. 169 | 170 | try 171 | { 172 | throw exc(); 173 | } 174 | // If copy of e throws, std::terminate should be called 175 | catch (exc e) 176 | { 177 | } 178 | 179 | try 180 | { 181 | throw exc(); 182 | } 183 | catch (exc & e_ref) 184 | { 185 | // Propagates exception as expected 186 | exc e(e_ref); 187 | } 188 | 189 | The `__CxxThrowException` function is written in assembly, as we depend on 190 | the format of its stack frame to 191 | 192 | * perform rethrows, and to 193 | * destroy the exception object if another exception is thrown. 194 | -------------------------------------------------------------------------------- /src/x64/capture.asm: -------------------------------------------------------------------------------- 1 | extern __cxx_dispatch_exception: proc 2 | extern __cxx_destroy_exception: proc 3 | extern __cxx_seh_frame_handler: proc 4 | extern __cxx_call_catch_frame_handler: proc 5 | 6 | ; The stack frame of `__CxxThrowException` always contains 7 | ; the catch info structure defined below. During the unwind, 8 | ; the referenced exception object may be destroyed, or reused 9 | ; in case of a rethrow. Furthermore, the `primary_frame_ptr` and 10 | ; `unwind_context` are captured and can be used by the next C++ frame handler. 11 | 12 | catch_info_t struct 13 | cont_addr_0 qword ? 14 | cont_addr_1 qword ? 15 | primary_frame_ptr qword ? 16 | exception_object_or_link qword ? 17 | throw_info_if_owner qword ? 18 | unwind_context qword ? 19 | catch_info_t ends 20 | 21 | ; The __CxxThrowException allocates `throw_fr` as its frame. This is 22 | ; a fairly large structure containing the non-volatile context of the calling 23 | ; function. `rip` and `rsp` are layed out in a machine frame format, so that 24 | ; they can be applied by the `iretq` instruction. 25 | ; 26 | ; The context starts out marked by `.allocstack`. However, once the context is 27 | ; unwound, the execution falls through to different funclet, in which the same 28 | ; frame structure is marked with `.pushframe` and a bunch of `.pushreg`s. 29 | ; At that point, it is safe to modify non-volatile registers -- this is the 30 | ; point at which the context is applied, save the machine frame. 31 | ; 32 | ; After that, through another fallthrough, the frame is transformed into 33 | ; a `catch_fr`, which is significantly smaller. `catch_info` and the machine 34 | ; frame stay in the same place. The exception object's copy constructor and 35 | ; then the catch handler funclet are called on top of this smaller stack. 36 | ; 37 | ; The stack looks like this. 38 | ; 39 | ; /---------------------------\ 40 | ; | catch funclet | 41 | ; +---------------------------+ 42 | ; | __cxx_call_catch_handler | 43 | ; | | 44 | ; | catch_fr: | 45 | ; | 0x00: red zone | 46 | ; | 0x20: machine frame | 47 | ; | 0x48: catch info | 48 | ; | 0x68: contents of the | 49 | ; | unwound frames, | 50 | ; | including the live | 51 | ; | exception object | 52 | ; +---------------------------+ 53 | ; | some other funclet | 54 | ; +---------------------------+ 55 | ; | | 56 | ; 57 | ; The catch funclet returns the address at which the execution should continue 58 | ; in the lower frame. We therefore update the `rip` in the machine frame 59 | ; and then `iretq`. This leaves the stack in the following state. 60 | ; 61 | ; /---------------------------\ 62 | ; | some other funclet | 63 | ; +---------------------------+ 64 | ; | | 65 | 66 | throw_fr struct 67 | p1 qword ? 68 | p2 qword ? 69 | p3 qword ? 70 | p4 qword ? 71 | 72 | $xmm6 oword ? ; 0x40 73 | $xmm7 oword ? ; 0x50 74 | $xmm8 oword ? ; 0x60 75 | $xmm9 oword ? ; 0x70 76 | $xmm10 oword ? ; 0x80 77 | $xmm11 oword ? ; 0x90 78 | $xmm12 oword ? ; 0xa0 79 | $xmm13 oword ? ; 0xb0 80 | $xmm14 oword ? ; 0xc0 81 | $xmm15 oword ? ; 0xd0 82 | 83 | $rbx qword ? ; 0x00 84 | $rbp qword ? ; 0x08 85 | $rsi qword ? ; 0x10 86 | $rdi qword ? ; 0x18 87 | $r12 qword ? ; 0x20 88 | $r13 qword ? ; 0x28 89 | $r14 qword ? ; 0x30 90 | $r15 qword ? ; 0x38 91 | 92 | $rip qword ? 93 | $cs qword ? 94 | eflags qword ? 95 | $rsp qword ? 96 | $ss qword ? 97 | 98 | catch_info catch_info_t <> 99 | throw_fr ends 100 | 101 | catch_fr struct 102 | p1 qword ? 103 | p2 qword ? 104 | p3 qword ? 105 | p4 qword ? 106 | 107 | $rip qword ? 108 | $cs qword ? 109 | eflags qword ? 110 | $rsp qword ? 111 | $ss qword ? 112 | 113 | catch_info catch_info_t <> 114 | catch_fr ends 115 | 116 | .code 117 | 118 | __NLG_Return2 proc public 119 | ret 120 | __NLG_Return2 endp 121 | 122 | __NLG_Dispatch2 proc public 123 | ret 124 | __NLG_Dispatch2 endp 125 | 126 | _CxxThrowException proc public frame: __cxx_seh_frame_handler 127 | ; We receive two arguments, the pointer to the exception object 128 | ; and a const pointer to `cxx_throw_info`. In case of a rethrow, 129 | ; both of these are null. 130 | ; 131 | ; We need to 132 | ; 133 | ; * walk the stack in the search of a catch handler, 134 | ; * unwind the frames along the way, 135 | ; * construct the catch variable for the handler (if any), 136 | ; * call the handler, and 137 | ; * destroy the exception object, unless another frame owns it. 138 | ; 139 | ; Most of the work is done by `__cxx_dispatch_exception`, which unwinds 140 | ; the stack and fills in a catch info structure containing 141 | ; everything we need to construct the catch var, call the handler, 142 | ; and destroy the exception object. 143 | ; 144 | ; We must capture our caller's non-volatile context, then reapply it 145 | ; after it is modified by the dispatcher. 146 | ; 147 | ; The layout of `throw_fr` is purpusfully made to contain a machine context 148 | ; so that we can `iretq` to free the stack while keeping the .pdata-based 149 | ; callstack valid at every point during the exception handling. 150 | 151 | mov r10, [rsp] 152 | lea r11, [rsp + 8] 153 | 154 | sub rsp, (sizeof throw_fr) - throw_fr.catch_info 155 | .allocstack (sizeof throw_fr) - throw_fr.catch_info 156 | 157 | ; We'll be filling the frame gradually to keep rsp offsets small 158 | ; and thus to keep the opcodes small. 159 | 160 | mov eax, ss 161 | push rax 162 | .allocstack 8 163 | 164 | push r11 165 | .allocstack 8 166 | 167 | pushfq 168 | .allocstack 8 169 | 170 | mov eax, cs 171 | push rax 172 | .allocstack 8 173 | 174 | push r10 175 | .allocstack 8 176 | 177 | push r15 178 | .allocstack 8 179 | push r14 180 | .allocstack 8 181 | push r13 182 | .allocstack 8 183 | push r12 184 | .allocstack 8 185 | push rdi 186 | .allocstack 8 187 | push rsi 188 | .allocstack 8 189 | push rbp 190 | .allocstack 8 191 | push rbx 192 | .allocstack 8 193 | 194 | sub rsp, throw_fr.$rbx 195 | .allocstack throw_fr.$rbx 196 | .endprolog 197 | 198 | ; We index via `rax`, which points into the middle of the xmm context. 199 | ; This makes the offset fit in a signed byte, making the opcodes shorter. 200 | base = throw_fr.$xmm10 201 | lea rax, [rsp + base] 202 | movdqa [rax - base + throw_fr.$xmm6], xmm6 203 | movdqa [rax - base + throw_fr.$xmm7], xmm7 204 | movdqa [rax - base + throw_fr.$xmm8], xmm8 205 | movdqa [rax - base + throw_fr.$xmm9], xmm9 206 | movdqa [rax - base + throw_fr.$xmm10], xmm10 207 | movdqa [rax - base + throw_fr.$xmm11], xmm11 208 | movdqa [rax - base + throw_fr.$xmm12], xmm12 209 | movdqa [rax - base + throw_fr.$xmm13], xmm13 210 | movdqa [rax - base + throw_fr.$xmm14], xmm14 211 | movdqa [rax - base + throw_fr.$xmm15], xmm15 212 | 213 | ; The dispatcher walks the function frames on the stack and looks for 214 | ; the C++ catch handler. During the walk it unwinds the frames. 215 | ; If the handler is not found, the dispatcher MUST never return, 216 | ; it should assert or bugcheck or whatever. 217 | ; 218 | ; Once the target frame is found and potentially partially unwound, 219 | ; the dispatcher will construct the catch variable, if any. 220 | ; 221 | ; The function should be marked `noexcept` to make sure that a throw 222 | ; in one of the destructors causes `std::terminate`, instead of 223 | ; unwinding already unwound frames. 224 | ; 225 | ; The dispatcher receives as arguments 226 | ; 227 | ; * the pointer to the exception object to throw, 228 | ; * the pointer to read-only `cxx_throw_info` structure, 229 | ; * the pointer to the throw frame object we just partially filled in. 230 | ; 231 | ; Note that nothing except for the captured context is initialized. 232 | ; 233 | ; Here, `rcx` and `rdx` are already filled by our caller. 234 | 235 | lea r8, [rsp] 236 | call __cxx_dispatch_exception 237 | 238 | ; Here, `rax` contains the address of the catch handler funclet we'll be 239 | ; calling later. We eventually need to move it to `rcx` for NLG notification 240 | ; and it makes sense to do it here, since we need at least one more 241 | ; instruction in this .pdata context. 242 | 243 | mov rcx, rax 244 | 245 | ; Now that the dispatcher returned, the non-volatile context should be 246 | ; modified to that of the catcher's frame and all the fields should be 247 | ; filled in. We can now reclain some stack space, but we must keep 248 | ; the exception object, which lives in the thrower's frame, alive. 249 | ; Since the thrower is right below us, the best we can do is make our own 250 | ; stack smaller. 251 | ; 252 | ; As such, we now immediately apply the non-volatile context and 253 | ; free it from the stack, except for the machine frame. Given our .pdata 254 | ; context, we mustn't modify non-volatile registers here. Instead, we fall 255 | ; through to another funclet with a different .pdata setup. 256 | _CxxThrowException endp 257 | 258 | __cxx_eh_apply_context proc private frame: __cxx_seh_frame_handler 259 | ; This function expects its frame to have already been allocated and that 260 | ; essentially means that it shouldn't really be called. 261 | ; 262 | ; The .pdata for this function is setup so that the non-volatile context 263 | ; filled in by the previous funclet is considered when walking the stack. 264 | ; Through `.pushframe`, all stack frames above the catcher's become 265 | ; part of our frame. All exceptions thrown from now on will not 266 | ; unwind any unwound frames again. Futhermore, we can write 267 | ; into the non-volatile registers without it affecting the callstack. 268 | .pushframe 269 | .pushreg r15 270 | .pushreg r14 271 | .pushreg r13 272 | .pushreg r12 273 | .pushreg rdi 274 | .pushreg rsi 275 | .pushreg rbp 276 | .pushreg rbx 277 | .allocstack throw_fr.$rbx 278 | .savexmm128 xmm6, throw_fr.$xmm6 279 | .savexmm128 xmm7, throw_fr.$xmm7 280 | .savexmm128 xmm8, throw_fr.$xmm8 281 | .savexmm128 xmm9, throw_fr.$xmm9 282 | .savexmm128 xmm10, throw_fr.$xmm10 283 | .savexmm128 xmm11, throw_fr.$xmm11 284 | .savexmm128 xmm12, throw_fr.$xmm12 285 | .savexmm128 xmm13, throw_fr.$xmm13 286 | .savexmm128 xmm14, throw_fr.$xmm14 287 | .savexmm128 xmm15, throw_fr.$xmm15 288 | .endprolog 289 | 290 | base = throw_fr.$xmm10 291 | lea rax, [rsp + base] 292 | movdqa xmm6, [rax - base + throw_fr.$xmm6] 293 | movdqa xmm7, [rax - base + throw_fr.$xmm7] 294 | movdqa xmm8, [rax - base + throw_fr.$xmm8] 295 | movdqa xmm9, [rax - base + throw_fr.$xmm9] 296 | movdqa xmm10, [rax - base + throw_fr.$xmm10] 297 | movdqa xmm11, [rax - base + throw_fr.$xmm11] 298 | movdqa xmm12, [rax - base + throw_fr.$xmm12] 299 | movdqa xmm13, [rax - base + throw_fr.$xmm13] 300 | movdqa xmm14, [rax - base + throw_fr.$xmm14] 301 | movdqa xmm15, [rax - base + throw_fr.$xmm15] 302 | 303 | base = throw_fr.$rdi 304 | lea rax, [rax + base - throw_fr.$xmm10] 305 | mov rbx, [rax - base + throw_fr.$rbx] 306 | mov rbp, [rax - base + throw_fr.$rbp] 307 | mov rsi, [rax - base + throw_fr.$rsi] 308 | mov rdi, [rax - base + throw_fr.$rdi] 309 | mov r12, [rax - base + throw_fr.$r12] 310 | mov r13, [rax - base + throw_fr.$r13] 311 | mov r14, [rax - base + throw_fr.$r14] 312 | mov r15, [rax - base + throw_fr.$r15] 313 | 314 | ; The following instruction will turn `throw_fr` into `catch_fr`, 315 | ; essentially removing the non-volatile context, which has already been 316 | ; applied, from the stack. Since we're moving the frame pointer, we must 317 | ; change .pdata again. 318 | 319 | lea rsp, [rax - base + (sizeof throw_fr) - (sizeof catch_fr)] 320 | __cxx_eh_apply_context endp 321 | 322 | __cxx_call_catch_handler proc public frame: __cxx_call_catch_frame_handler 323 | .pushframe 324 | .allocstack catch_fr.$rip 325 | .endprolog 326 | ; We now have a small frame, so we can call the handler. Everything can 327 | ; throw now and our frame handler must be ready to free the exception 328 | ; object if it happens. 329 | ; 330 | ; It's unclear why the handler expects the frame pointer in `rdx` rather 331 | ; than `rcx`. The Microsoft's implementation leave the handler's function 332 | ; pointer in `rcx`, so we're going to follow suit, just to be sure. 333 | 334 | mov rdx, [rsp + catch_fr.catch_info.primary_frame_ptr] 335 | mov r8, 1 336 | call __NLG_Dispatch2 337 | call rcx 338 | call __NLG_Return2 339 | 340 | ; The handler returns the continuation address. Apply it to the machine 341 | ; frame; this changes the callstack. 342 | 343 | cmp rax, 1 344 | ja _direct_continuation_address 345 | mov rax, [rsp + catch_fr.catch_info.cont_addr_0 + 8*rax] 346 | _direct_continuation_address: 347 | 348 | mov [rsp + catch_fr.$rip], rax 349 | 350 | mov rcx, rax 351 | mov rdx, [rsp + catch_fr.catch_info.primary_frame_ptr] 352 | mov r8, 2 353 | call __NLG_Dispatch2 354 | 355 | ; One more change of the current .pdata entry. Although 356 | ; `__cxx_destroy_exception` is noexcept, we don't want any rethrow 357 | ; probes to look at our frame while the exception object is being 358 | ; destroyed. 359 | __cxx_call_catch_handler endp 360 | 361 | __cxx_call_exception_destructor proc private frame: __cxx_seh_frame_handler 362 | .pushframe 363 | .allocstack catch_fr.$rip 364 | .endprolog 365 | ; Now destroy the exception object. 366 | 367 | lea rcx, [rsp + catch_fr.catch_info] 368 | call __cxx_destroy_exception 369 | 370 | ; We'd love to iretq here, but as usual, the deallocation of our frame 371 | ; moves `rsp` and `iretq` can't be in the function's epilog. 372 | ; One last switch of the .pdata context is necessary. 373 | 374 | add rsp, catch_fr.$rip 375 | __cxx_call_exception_destructor endp 376 | 377 | __cxx_continue_after_exception proc frame: __cxx_seh_frame_handler 378 | .pushframe 379 | .endprolog 380 | iretq 381 | __cxx_continue_after_exception endp 382 | 383 | end 384 | -------------------------------------------------------------------------------- /src/x64/cpu_context.h: -------------------------------------------------------------------------------- 1 | #include "../stdint.h" 2 | #include "../win32_seh.h" 3 | #include "../rva.h" 4 | 5 | namespace vcrtl::_msvc::x64 { 6 | 7 | enum class unwind_code_t: uint8_t 8 | { 9 | push_nonvolatile_reg = 0, // 1 10 | alloc_large = 1, // 2-3 11 | alloc_small = 2, // 1 12 | set_frame_pointer = 3, // 1 13 | save_nonvolatile_reg = 4, // 2 14 | save_nonvolatile_reg_far = 5, // 3 15 | epilog = 6, // 2 16 | _07 = 7, // 3 17 | save_xmm128 = 8, // 2 18 | save_xmm128_far = 9, // 3 19 | push_machine_frame = 10, // 1 20 | }; 21 | 22 | struct pe_unwind_entry 23 | { 24 | uint8_t prolog_offset; 25 | unwind_code_t code: 4; 26 | uint8_t info: 4; 27 | }; 28 | 29 | struct pe_unwind_info 30 | { 31 | uint8_t version: 3; 32 | uint8_t flags: 5; 33 | uint8_t prolog_size; 34 | uint8_t unwind_code_count; 35 | uint8_t frame_reg: 4; 36 | uint8_t frame_reg_disp: 4; 37 | union 38 | { 39 | pe_unwind_entry entries[1]; 40 | uint16_t data[1]; 41 | }; 42 | }; 43 | 44 | struct pe_function 45 | { 46 | /* 0x00 */ rva begin; 47 | /* 0x04 */ rva end; 48 | /* 0x08 */ rva unwind_info; 49 | }; 50 | 51 | /*struct frame_info_t 52 | { 53 | rva function; 54 | rva exception_routine; 55 | rva extra_data; 56 | rva rip; 57 | uint64_t frame_ptr; 58 | };*/ 59 | 60 | struct alignas(16) xmm_t 61 | { 62 | char _data[16]; 63 | }; 64 | 65 | struct frame_walk_context_t 66 | { 67 | xmm_t xmm6; 68 | xmm_t xmm7; 69 | xmm_t xmm8; 70 | xmm_t xmm9; 71 | xmm_t xmm10; 72 | xmm_t xmm11; 73 | xmm_t xmm12; 74 | xmm_t xmm13; 75 | xmm_t xmm14; 76 | xmm_t xmm15; 77 | 78 | uint64_t rbx; 79 | uint64_t rbp; 80 | uint64_t rsi; 81 | uint64_t rdi; 82 | uint64_t r12; 83 | uint64_t r13; 84 | uint64_t r14; 85 | uint64_t r15; 86 | 87 | uint64_t & gp(uint8_t idx); 88 | }; 89 | 90 | struct mach_frame_t 91 | { 92 | byte const * rip; 93 | uint64_t cs; 94 | uint64_t eflags; 95 | uint64_t rsp; 96 | uint64_t ss; 97 | }; 98 | 99 | struct frame_walk_pdata_t 100 | { 101 | static frame_walk_pdata_t for_this_image(); 102 | static frame_walk_pdata_t from_image_base(byte const * image_base); 103 | 104 | byte const * image_base() const; 105 | bool contains_address(byte const * addr) const; 106 | pe_function const * find_function_entry(byte const * addr) const; 107 | 108 | void unwind(pe_unwind_info const & unwind_info, frame_walk_context_t & ctx, mach_frame_t & mach) const; 109 | 110 | private: 111 | explicit frame_walk_pdata_t(byte const * image_base); 112 | 113 | byte const * _image_base; 114 | pe_function const * _functions; 115 | uint32_t _function_count; 116 | uint32_t _image_size; 117 | }; 118 | 119 | 120 | } 121 | 122 | #pragma once 123 | -------------------------------------------------------------------------------- /src/x64/eh_structs_x64.h: -------------------------------------------------------------------------------- 1 | #include "../stdint.h" 2 | #include "../ptr_to_member.h" 3 | #include "../rva.h" 4 | 5 | namespace vcrtl::_msvc::x64 { 6 | 7 | struct gs_handler_data_t 8 | { 9 | static constexpr uint32_t EHandler = 1; 10 | static constexpr uint32_t UHandler = 2; 11 | static constexpr uint32_t HasAlignment = 4; 12 | static constexpr uint32_t CookieOffsetMask = ~7u; 13 | 14 | uint32_t cookie_offset; 15 | int32_t aligned_base_offset; 16 | int32_t alignment; 17 | }; 18 | 19 | struct c_handler_entry_t 20 | { 21 | uint32_t try_rva_low; 22 | uint32_t try_rva_high; 23 | union 24 | { 25 | rva filter; 26 | rva unwinder; 27 | }; 28 | uint32_t target_rva; 29 | }; 30 | 31 | struct c_handler_data_t 32 | { 33 | uint32_t entry_count; 34 | c_handler_entry_t entries[1]; 35 | }; 36 | 37 | enum class cxx_catchable_property: uint32_t 38 | { 39 | is_simple_type = 0x01, 40 | by_reference_only = 0x02, 41 | has_virtual_base = 0x04, 42 | is_winrt_handle = 0x08, 43 | is_std_bad_alloc = 0x10, 44 | }; 45 | 46 | struct cxx_catchable_type 47 | { 48 | flags properties; 49 | rva desc; 50 | cxx_ptr_to_member offset; 51 | uint32_t size; 52 | rva copy_fn; 53 | }; 54 | 55 | struct cxx_catchable_type_list 56 | { 57 | uint32_t count; 58 | rva types[1]; 59 | }; 60 | 61 | enum class cxx_throw_flag: uint32_t 62 | { 63 | is_const = 0x01, 64 | is_volatile = 0x02, 65 | is_unaligned = 0x04, 66 | is_pure = 0x08, 67 | is_winrt = 0x10, 68 | }; 69 | 70 | struct cxx_throw_info 71 | { 72 | flags attributes; 73 | rva destroy_exc_obj; 74 | rva compat_fn; 75 | rva catchables; 76 | }; 77 | 78 | enum class cxx_catch_flag: uint32_t 79 | { 80 | is_const = 1, 81 | is_volatile = 2, 82 | is_unaligned = 4, 83 | is_reference = 8, 84 | is_resumable = 0x10, 85 | is_ellipsis = 0x40, 86 | is_bad_alloc_compat = 0x80, 87 | is_complus_eh = 0x80000000, 88 | }; 89 | 90 | struct cxx_eh_node_catch 91 | { 92 | byte * primary_frame_ptr; 93 | }; 94 | 95 | struct cxx_catch_handler 96 | { 97 | flags adjectives; 98 | rva type_desc; 99 | rva catch_object_offset; 100 | rva handler; 101 | rva node_offset; 102 | }; 103 | 104 | struct cxx_try_block 105 | { 106 | /* 0x00 */ int32_t try_low; 107 | /* 0x04 */ int32_t try_high; 108 | /* 0x08 */ int32_t catch_high; 109 | /* 0x0c */ int32_t catch_count; 110 | /* 0x10 */ rva catch_handlers; 111 | }; 112 | 113 | struct cxx_eh_region 114 | { 115 | rva first_ip; 116 | int32_t state; 117 | }; 118 | 119 | struct alignas(8) cxx_eh_node 120 | { 121 | int32_t state; 122 | int32_t _unused; 123 | }; 124 | 125 | struct cxx_unwind_graph_edge 126 | { 127 | int32_t next; 128 | rva cleanup_handler; 129 | }; 130 | 131 | enum class cxx_eh_flag : uint32_t 132 | { 133 | compiled_with_ehs = 1, 134 | is_noexcept = 4, 135 | }; 136 | 137 | struct cxx_function_eh_info 138 | { 139 | /* 0x00 */ uint32_t _magic; 140 | /* 0x04 */ uint32_t state_count; 141 | /* 0x08 */ rva unwind_graph; 142 | /* 0x0c */ int32_t try_block_count; 143 | /* 0x10 */ rva try_blocks; 144 | /* 0x14 */ uint32_t region_count; 145 | /* 0x18 */ rva regions; 146 | /* 0x1c */ rva eh_node_offset; 147 | 148 | // `magic_num >= 0x19930521` 149 | /* 0x20 */ uint32_t es_types; 150 | 151 | // `magic_num >= 0x19930522` 152 | /* 0x24 */ flags eh_flags; 153 | }; 154 | 155 | struct cxx_eh_handler_data 156 | { 157 | rva eh_info; 158 | }; 159 | 160 | } 161 | 162 | #pragma once 163 | -------------------------------------------------------------------------------- /src/x64/fh3.cpp: -------------------------------------------------------------------------------- 1 | #include "throw.h" 2 | #include "../assert.h" 3 | using namespace vcrtl; 4 | using namespace vcrtl::_msvc; 5 | using namespace vcrtl::_msvc::x64; 6 | 7 | struct fh3_fn_ctx_t 8 | { 9 | int32_t state; 10 | int32_t home_block_index; 11 | }; 12 | 13 | static win32_exception_disposition _frame_handler( 14 | win32_exception_record * _exception_record, 15 | byte * frame_ptr, 16 | x64_cpu_context * _cpu_ctx, 17 | cxx_dispatcher_context_t * dispatcher_context 18 | ) 19 | { 20 | (void)_exception_record; 21 | (void)_cpu_ctx; 22 | 23 | if (dispatcher_context->cookie == &rethrow_probe_cookie) 24 | return win32_exception_disposition::cxx_handler; 25 | if (dispatcher_context->cookie != &unwind_cookie) 26 | return win32_exception_disposition::continue_search; 27 | 28 | cxx_throw_frame_t * throw_frame = dispatcher_context->throw_frame; 29 | 30 | cxx_catch_info_t & catch_info = throw_frame->catch_info; 31 | fh3_fn_ctx_t & ctx = catch_info.unwind_context.as(); 32 | 33 | byte const * image_base = dispatcher_context->pdata->image_base(); 34 | 35 | cxx_function_eh_info const * eh_info = image_base + *(rva *)dispatcher_context->extra_data; 36 | cxx_try_block const * try_blocks = image_base + eh_info->try_blocks; 37 | 38 | byte * primary_frame_ptr = catch_info.primary_frame_ptr; 39 | int32_t state; 40 | int32_t home_block_index; 41 | 42 | if (primary_frame_ptr < frame_ptr) 43 | { 44 | rva pc_rva = make_rva(reinterpret_cast(throw_frame->mach.rip), image_base); 45 | 46 | auto regions = image_base + eh_info->regions; 47 | state = regions[eh_info->region_count - 1].state; 48 | for (uint32_t i = 1; i != eh_info->region_count; ++i) 49 | { 50 | if (pc_rva < regions[i].first_ip) 51 | { 52 | state = regions[i - 1].state; 53 | break; 54 | } 55 | } 56 | 57 | home_block_index = eh_info->try_block_count - 1; 58 | } 59 | else 60 | { 61 | state = ctx.state; 62 | home_block_index = ctx.home_block_index; 63 | } 64 | 65 | int32_t funclet_low_state = -1; 66 | if (primary_frame_ptr == frame_ptr) 67 | { 68 | home_block_index = -1; 69 | } 70 | else 71 | { 72 | // Identify the funclet we're currently in. If we're in a catch 73 | // funclet, locate the primary frame ptr and cache everything. 74 | for (; home_block_index > -1; --home_block_index) 75 | { 76 | cxx_try_block const & try_block = try_blocks[home_block_index]; 77 | if (try_block.try_high < state && state <= try_block.catch_high) 78 | { 79 | if (primary_frame_ptr < frame_ptr) 80 | { 81 | cxx_catch_handler const * catch_handlers = image_base + try_block.catch_handlers; 82 | for (int32_t j = 0; j < try_block.catch_count; ++j) 83 | { 84 | cxx_catch_handler const & catch_handler = catch_handlers[j]; 85 | if (catch_handler.handler == dispatcher_context->fnent->begin) 86 | { 87 | cxx_eh_node_catch const * node = frame_ptr + catch_handler.node_offset; 88 | primary_frame_ptr = node->primary_frame_ptr; 89 | verify(primary_frame_ptr >= frame_ptr); 90 | break; 91 | } 92 | } 93 | } 94 | 95 | funclet_low_state = try_block.try_low; 96 | break; 97 | } 98 | } 99 | 100 | if (primary_frame_ptr < frame_ptr) 101 | primary_frame_ptr = frame_ptr; 102 | } 103 | 104 | int32_t target_state = funclet_low_state; 105 | 106 | cxx_throw_info const * throw_info = catch_info.get_throw_info(); 107 | 108 | cxx_catch_handler const * target_catch_handler = nullptr; 109 | for (int32_t i = home_block_index + 1; !target_catch_handler && i < eh_info->try_block_count; ++i) 110 | { 111 | auto const & try_block = try_blocks[i]; 112 | 113 | if (try_block.try_low <= state && state <= try_block.try_high) 114 | { 115 | if (try_block.try_low < funclet_low_state) 116 | continue; 117 | 118 | if (!throw_info) 119 | { 120 | probe_for_exception_object(*dispatcher_context->pdata, *throw_frame); 121 | throw_info = catch_info.get_throw_info(); 122 | } 123 | 124 | cxx_catch_handler const * handlers = image_base + try_block.catch_handlers; 125 | for (int32_t j = 0; j < try_block.catch_count; ++j) 126 | { 127 | cxx_catch_handler const & catch_block = handlers[j]; 128 | 129 | if (!process_catch_block(image_base, catch_block.adjectives, image_base + catch_block.type_desc, 130 | primary_frame_ptr + catch_block.catch_object_offset, catch_info.get_exception_object(), 131 | *throw_info)) 132 | { 133 | continue; 134 | } 135 | 136 | target_state = try_block.try_low - 1; 137 | target_catch_handler = &catch_block; 138 | dispatcher_context->handler = image_base + catch_block.handler; 139 | 140 | catch_info.primary_frame_ptr = primary_frame_ptr; 141 | ctx.home_block_index = home_block_index; 142 | ctx.state = try_block.try_low - 1; 143 | break; 144 | } 145 | } 146 | } 147 | 148 | if (home_block_index == -1 && !target_catch_handler 149 | && eh_info->eh_flags.has_any_of(cxx_eh_flag::is_noexcept)) 150 | { 151 | // XXX call std::terminate 152 | verify(false); 153 | } 154 | 155 | verify(target_state >= funclet_low_state); 156 | 157 | cxx_unwind_graph_edge const * unwind_graph = image_base + eh_info->unwind_graph; 158 | while (state > target_state) 159 | { 160 | cxx_unwind_graph_edge const & edge = unwind_graph[state]; 161 | state = edge.next; 162 | if (edge.cleanup_handler) 163 | { 164 | byte const * funclet = image_base + edge.cleanup_handler; 165 | using fn_type = uintptr_t(byte const * funclet, byte const * frame_ptr); 166 | (void)((fn_type *)funclet)(funclet, frame_ptr); 167 | } 168 | } 169 | 170 | return win32_exception_disposition::cxx_handler; 171 | } 172 | 173 | extern "C" x64_frame_handler_t __CxxFrameHandler3; 174 | extern "C" win32_exception_disposition __CxxFrameHandler3( 175 | win32_exception_record * exception_record, 176 | byte * frame_ptr, 177 | x64_cpu_context * cpu_ctx, 178 | void * ctx 179 | ) 180 | { 181 | if (exception_record) 182 | { 183 | verify(!exception_record->flags.has_any_of(win32_exception_flag::unwinding)); 184 | return win32_exception_disposition::continue_search; 185 | } 186 | 187 | return _frame_handler(exception_record, frame_ptr, cpu_ctx, static_cast(ctx)); 188 | } 189 | 190 | extern "C" x64_frame_handler_t __GSHandlerCheck_EH; 191 | extern "C" win32_exception_disposition __GSHandlerCheck_EH( 192 | win32_exception_record * exception_record, 193 | byte * frame_ptr, 194 | x64_cpu_context * cpu_ctx, 195 | void * ctx 196 | ) 197 | { 198 | if (exception_record) 199 | { 200 | verify(!exception_record->flags.has_any_of(win32_exception_flag::unwinding)); 201 | return win32_exception_disposition::continue_search; 202 | } 203 | 204 | return _frame_handler(exception_record, frame_ptr, cpu_ctx, static_cast(ctx)); 205 | } 206 | -------------------------------------------------------------------------------- /src/x64/fh4.cpp: -------------------------------------------------------------------------------- 1 | #include "cpu_context.h" 2 | #include "throw.h" 3 | #include "../utils.h" 4 | #include "../assert.h" 5 | 6 | using namespace vcrtl; 7 | using namespace vcrtl::_msvc; 8 | using namespace vcrtl::_msvc::x64; 9 | 10 | struct gs_check_data_t 11 | { 12 | uint32_t _00; 13 | }; 14 | 15 | struct gs4_data_t 16 | { 17 | rva func_info; 18 | gs_check_data_t gs_check_data; 19 | }; 20 | 21 | enum class eh4_flag: uint8_t 22 | { 23 | is_catch_funclet = 0x01, 24 | has_multiple_funclets = 0x02, 25 | bbt = 0x04, // Flags set by Basic Block Transformations 26 | has_unwind_map = 0x08, 27 | has_try_block_map = 0x10, 28 | ehs = 0x20, 29 | is_noexcept = 0x40, 30 | }; 31 | 32 | struct fh4_info 33 | { 34 | flags flags; 35 | uint32_t bbt_flags; 36 | rva unwind_graph; 37 | rva try_blocks; 38 | rva regions; 39 | rva primary_frame_ptr; 40 | }; 41 | 42 | static uint32_t read_unsigned(uint8_t const ** data) 43 | { 44 | uint8_t enc_type = (*data)[0] & 0xf; 45 | 46 | static constexpr uint8_t lengths[] = { 47 | 1, 2, 1, 3, 1, 2, 1, 4, 48 | 1, 2, 1, 3, 1, 2, 1, 5, 49 | }; 50 | static constexpr uint8_t shifts[] = { 51 | 0x19, 0x12, 0x19, 0x0b, 0x19, 0x12, 0x19, 0x04, 52 | 0x19, 0x12, 0x19, 0x0b, 0x19, 0x12, 0x19, 0x00, 53 | }; 54 | 55 | uint8_t length = lengths[enc_type]; 56 | 57 | // XXX we're in UB land here 58 | uint32_t r = *(uint32_t *)(*data + length - 4); 59 | r >>= shifts[enc_type]; 60 | 61 | *data += length; 62 | return r; 63 | } 64 | 65 | static int32_t read_int(uint8_t const ** data) 66 | { 67 | // XXX alignment 68 | 69 | int32_t r = *(int32_t *)(*data); 70 | *data += 4; 71 | return r; 72 | } 73 | 74 | template 75 | static rva read_rva(uint8_t const ** data) 76 | { 77 | uint32_t offset = read_int(data); 78 | return rva::from_displacement(offset); 79 | } 80 | 81 | static void _load_eh_info(fh4_info & eh_info, uint8_t const * data, byte const * image_base, pe_function const & fnent) 82 | { 83 | flags flags = (eh4_flag)*data++; 84 | eh_info.flags = flags; 85 | 86 | if (flags.has_any_of(eh4_flag::bbt)) 87 | eh_info.bbt_flags = read_unsigned(&data); 88 | 89 | if (flags.has_any_of(eh4_flag::has_unwind_map)) 90 | eh_info.unwind_graph = read_rva(&data); 91 | 92 | if (flags.has_any_of(eh4_flag::has_try_block_map)) 93 | eh_info.try_blocks = read_rva(&data); 94 | 95 | if (flags.has_any_of(eh4_flag::has_multiple_funclets)) 96 | { 97 | uint8_t const * funclet_map = image_base + read_rva(&data); 98 | 99 | uint32_t count = read_unsigned(&funclet_map); 100 | eh_info.regions = nullptr; 101 | 102 | for (uint32_t idx = 0; idx != count; ++idx) 103 | { 104 | rva fn_rva = read_rva(&funclet_map); 105 | rva regions = read_rva(&funclet_map); 106 | 107 | if (fn_rva == fnent.begin) 108 | { 109 | eh_info.regions = regions; 110 | break; 111 | } 112 | } 113 | } 114 | else 115 | { 116 | eh_info.regions = read_rva(&data); 117 | } 118 | 119 | if (flags.has_any_of(eh4_flag::is_catch_funclet)) 120 | eh_info.primary_frame_ptr = rva::from_displacement(read_unsigned(&data)); 121 | } 122 | 123 | static int32_t _lookup_region(fh4_info const * eh_info, byte const * image_base, rva fn, byte const * control_pc) 124 | { 125 | if (!eh_info->regions) 126 | return -1; 127 | 128 | rva pc = make_rva(control_pc, image_base + fn); 129 | uint8_t const * p = image_base + eh_info->regions; 130 | 131 | int32_t state = -1; 132 | uint32_t region_count = read_unsigned(&p); 133 | rva fn_rva = nullptr; 134 | for (uint32_t idx = 0; idx != region_count; ++idx) 135 | { 136 | fn_rva += read_unsigned(&p); 137 | if (pc < fn_rva) 138 | break; 139 | 140 | state = read_unsigned(&p) - 1; 141 | } 142 | 143 | return state; 144 | } 145 | 146 | enum class catch_block_flag: uint8_t 147 | { 148 | has_type_flags = 0x01, 149 | has_type_info = 0x02, 150 | has_catch_var = 0x04, 151 | image_rva = 0x08, 152 | continuation_addr_count = 0x30, 153 | }; 154 | 155 | enum class unwind_edge_type 156 | { 157 | trivial = 0, 158 | object_offset = 1, 159 | object_ptr_offset = 2, 160 | function = 3, 161 | }; 162 | 163 | struct unwind_edge_t 164 | { 165 | uint32_t target_offset; 166 | unwind_edge_type type; 167 | rva destroy_fn; 168 | rva object; 169 | 170 | static unwind_edge_t read(uint8_t const ** p) 171 | { 172 | unwind_edge_t r; 173 | 174 | uint32_t target_offset_and_type = read_unsigned(p); 175 | unwind_edge_type type = static_cast(target_offset_and_type & 3); 176 | r.type = type; 177 | r.target_offset = target_offset_and_type >> 2; 178 | r.destroy_fn = type != unwind_edge_type::trivial? read_rva(p): 0; 179 | r.object = rva::from_displacement( 180 | type == unwind_edge_type::object_offset || type == unwind_edge_type::object_ptr_offset? read_unsigned(p): 0); 181 | 182 | return r; 183 | } 184 | 185 | static void skip(uint8_t const ** p) 186 | { 187 | (void)read(p); 188 | } 189 | }; 190 | 191 | static win32_exception_disposition _frame_handler(byte * frame_ptr, cxx_dispatcher_context_t * dispatcher_context) 192 | { 193 | if (dispatcher_context->cookie == &rethrow_probe_cookie) 194 | return win32_exception_disposition::cxx_handler; 195 | if (dispatcher_context->cookie != &unwind_cookie) 196 | return win32_exception_disposition::continue_search; 197 | 198 | byte const * image_base = dispatcher_context->pdata->image_base(); 199 | cxx_throw_frame_t * throw_frame = dispatcher_context->throw_frame; 200 | cxx_catch_info_t & catch_info = throw_frame->catch_info; 201 | 202 | gs4_data_t const * handler_data = (gs4_data_t const *)dispatcher_context->extra_data; 203 | uint8_t const * compressed_data = image_base + handler_data->func_info; 204 | 205 | fh4_info eh_info = {}; 206 | _load_eh_info(eh_info, compressed_data, image_base, *dispatcher_context->fnent); 207 | 208 | byte * primary_frame_ptr; 209 | int32_t state; 210 | 211 | if (catch_info.primary_frame_ptr >= frame_ptr) 212 | { 213 | primary_frame_ptr = catch_info.primary_frame_ptr; 214 | state = catch_info.unwind_context.as(); 215 | } 216 | else 217 | { 218 | state = _lookup_region(&eh_info, image_base, dispatcher_context->fnent->begin, throw_frame->mach.rip); 219 | if (eh_info.flags.has_any_of(eh4_flag::is_catch_funclet)) 220 | primary_frame_ptr = *(frame_ptr + eh_info.primary_frame_ptr); 221 | else 222 | primary_frame_ptr = frame_ptr; 223 | } 224 | 225 | int32_t target_state = -1; 226 | 227 | if (eh_info.try_blocks && state >= 0) 228 | { 229 | cxx_throw_info const * throw_info = catch_info.get_throw_info(); 230 | 231 | uint8_t const * p = image_base + eh_info.try_blocks; 232 | uint32_t try_block_count = read_unsigned(&p); 233 | for (uint32_t i = 0; i != try_block_count; ++i) 234 | { 235 | uint32_t try_low = read_unsigned(&p); 236 | uint32_t try_high = read_unsigned(&p); 237 | 238 | uint32_t _catch_high = read_unsigned(&p); 239 | (void)_catch_high; 240 | 241 | rva handlers = read_rva(&p); 242 | 243 | if (try_low > (uint32_t)state || (uint32_t)state > try_high) 244 | continue; 245 | 246 | if (!throw_info) 247 | { 248 | probe_for_exception_object(*dispatcher_context->pdata, *throw_frame); 249 | throw_info = throw_frame->catch_info.get_throw_info(); 250 | } 251 | 252 | uint8_t const * q = image_base + handlers; 253 | uint32_t handler_count = read_unsigned(&q); 254 | 255 | for (uint32_t j = 0; j != handler_count; ++j) 256 | { 257 | flags handler_flags = flags(*q++); 258 | 259 | flags type_flags 260 | = handler_flags.has_any_of(catch_block_flag::has_type_flags) 261 | ? flags(read_unsigned(&q)) 262 | : nullflag; 263 | 264 | rva type 265 | = handler_flags.has_any_of(catch_block_flag::has_type_info) 266 | ? read_rva(&q) 267 | : 0; 268 | 269 | uint32_t continuation_addr_count = handler_flags.get(); 270 | 271 | rva catch_var 272 | = handler_flags.has_any_of(catch_block_flag::has_catch_var) 273 | ? rva::from_displacement(read_unsigned(&q)) 274 | : 0; 275 | 276 | rva handler = read_rva(&q); 277 | 278 | if (handler_flags.has_any_of(catch_block_flag::image_rva)) 279 | { 280 | for (uint32_t k = 0; k != continuation_addr_count; ++k) 281 | catch_info.continuation_address[k] = image_base + read_rva(&q); 282 | } 283 | else 284 | { 285 | byte const * fn = image_base + dispatcher_context->fnent->begin; 286 | for (uint32_t k = 0; k != continuation_addr_count; ++k) 287 | catch_info.continuation_address[k] = fn + read_unsigned(&q); 288 | } 289 | 290 | if (process_catch_block(image_base, type_flags, image_base + type, primary_frame_ptr + catch_var, 291 | throw_frame->catch_info.get_exception_object(), *throw_info)) 292 | { 293 | dispatcher_context->handler = image_base + handler; 294 | catch_info.primary_frame_ptr = primary_frame_ptr; 295 | target_state = try_low - 1; 296 | catch_info.unwind_context.as() = target_state; 297 | break; 298 | } 299 | } 300 | 301 | if (dispatcher_context->handler) 302 | break; 303 | } 304 | } 305 | 306 | if (target_state >= state) 307 | return win32_exception_disposition::cxx_handler; 308 | 309 | uint8_t const * p = image_base + eh_info.unwind_graph; 310 | uint32_t unwind_nodes = read_unsigned(&p); 311 | verify(state >= 0 && (uint32_t)state < unwind_nodes); 312 | 313 | uint8_t const * target_edge_last = p; 314 | for (int32_t i = 0; i != state; ++i) 315 | { 316 | unwind_edge_t::skip(&p); 317 | if (target_state + 1 == i) 318 | target_edge_last = p; 319 | } 320 | 321 | if (target_state + 1 == state) 322 | target_edge_last = p; 323 | 324 | for (;;) 325 | { 326 | uint8_t const * orig = p; 327 | 328 | uint32_t target_offset_and_type = read_unsigned(&p); 329 | uint32_t target_offset = target_offset_and_type >> 2; 330 | verify(target_offset != 0); 331 | 332 | unwind_edge_type edge_type = static_cast(target_offset_and_type & 3); 333 | 334 | switch (edge_type) 335 | { 336 | case unwind_edge_type::trivial: 337 | break; 338 | case unwind_edge_type::object_offset: 339 | { 340 | auto destroy_fn = image_base + read_rva(&p); 341 | byte * obj = frame_ptr + read_unsigned(&p); 342 | destroy_fn(obj); 343 | break; 344 | } 345 | case unwind_edge_type::object_ptr_offset: 346 | { 347 | auto destroy_fn = image_base + read_rva(&p); 348 | byte * obj = *reinterpret_cast(frame_ptr + read_unsigned(&p)); 349 | destroy_fn(obj); 350 | break; 351 | } 352 | case unwind_edge_type::function: 353 | { 354 | auto destroy_fn = image_base + read_rva(&p); 355 | destroy_fn(destroy_fn, frame_ptr); 356 | break; 357 | } 358 | } 359 | 360 | p = orig - target_offset; 361 | if (orig - target_edge_last < target_offset) 362 | break; 363 | } 364 | 365 | return win32_exception_disposition::cxx_handler; 366 | } 367 | 368 | extern "C" x64_frame_handler_t __CxxFrameHandler4; 369 | extern "C" win32_exception_disposition __CxxFrameHandler4( 370 | win32_exception_record * exception_record, 371 | byte * frame_ptr, 372 | x64_cpu_context * _cpu_ctx, 373 | void * ctx 374 | ) 375 | { 376 | (void)_cpu_ctx; 377 | if (exception_record) 378 | { 379 | verify(exception_record->flags.has_any_of(win32_exception_flag::unwinding)); 380 | return win32_exception_disposition::continue_search; 381 | } 382 | 383 | return _frame_handler(frame_ptr, static_cast(ctx)); 384 | } 385 | 386 | extern "C" x64_frame_handler_t __GSHandlerCheck_EH4; 387 | extern "C" win32_exception_disposition __GSHandlerCheck_EH4( 388 | win32_exception_record * exception_record, 389 | byte * frame_ptr, 390 | x64_cpu_context * _cpu_ctx, 391 | void * ctx 392 | ) 393 | { 394 | (void)_cpu_ctx; 395 | if (exception_record) 396 | { 397 | verify(exception_record->flags.has_any_of(win32_exception_flag::unwinding)); 398 | return win32_exception_disposition::continue_search; 399 | } 400 | 401 | return _frame_handler(frame_ptr, static_cast(ctx)); 402 | } 403 | -------------------------------------------------------------------------------- /src/x64/throw.cpp: -------------------------------------------------------------------------------- 1 | #include "throw.h" 2 | #include "../win32_seh.h" 3 | #include "../assert.h" 4 | #include "../memcpy.h" 5 | using namespace vcrtl; 6 | using namespace vcrtl::_msvc; 7 | using namespace vcrtl::_msvc::x64; 8 | 9 | extern "C" void __cxx_call_catch_handler(); 10 | extern "C" symbol __ImageBase; 11 | 12 | namespace { 13 | 14 | struct _catch_frame_t 15 | { 16 | uint64_t red_zone[4]; 17 | mach_frame_t mach; 18 | cxx_catch_info_t catch_info; 19 | }; 20 | 21 | } 22 | 23 | namespace vcrtl::_msvc::x64 { 24 | 25 | symbol unwind_cookie; 26 | symbol rethrow_probe_cookie; 27 | 28 | bool process_catch_block(byte const * image_base, flags adjectives, 29 | type_info const * match_type, void * catch_var, void * exception_object, cxx_throw_info const & throw_info) 30 | { 31 | if (adjectives.has_any_of(cxx_catch_flag::is_ellipsis)) 32 | return true; 33 | 34 | if (throw_info.attributes.has_any_of(cxx_throw_flag::is_const) 35 | && !adjectives.has_any_of(cxx_catch_flag::is_const)) 36 | { 37 | return false; 38 | } 39 | 40 | if (throw_info.attributes.has_any_of(cxx_throw_flag::is_unaligned) 41 | && !adjectives.has_any_of(cxx_catch_flag::is_unaligned)) 42 | { 43 | return false; 44 | } 45 | 46 | if (throw_info.attributes.has_any_of(cxx_throw_flag::is_volatile) 47 | && !adjectives.has_any_of(cxx_catch_flag::is_volatile)) 48 | { 49 | return false; 50 | } 51 | 52 | cxx_catchable_type_list const * catchable_list = image_base + throw_info.catchables; 53 | rva const * catchables = catchable_list->types; 54 | 55 | for (uint32_t i = 0; i != catchable_list->count; ++i) 56 | { 57 | cxx_catchable_type const * catchable = image_base + catchables[i]; 58 | type_info const * catchable_type_desc = image_base + catchable->desc; 59 | if (catchable_type_desc == match_type) 60 | { 61 | if (!catchable->properties.has_any_of(cxx_catchable_property::by_reference_only) 62 | || adjectives.has_any_of(cxx_catch_flag::is_reference)) 63 | { 64 | if (adjectives.has_any_of(cxx_catch_flag::is_reference)) 65 | { 66 | *(void **)catch_var = exception_object; 67 | } 68 | else 69 | { 70 | if (catchable->properties.has_any_of(cxx_catchable_property::is_simple_type)) 71 | { 72 | if (catchable->size == sizeof(uintptr_t)) 73 | { 74 | memcpy((void *)catch_var, (void *)exception_object, sizeof(uintptr_t)); 75 | uintptr_t & ptr = *(uintptr_t *)catch_var; 76 | if (ptr) 77 | ptr = catchable->offset.apply(ptr); 78 | } 79 | else 80 | { 81 | memcpy((void *)catch_var, (void *)exception_object, catchable->size); 82 | } 83 | } 84 | else if (!catchable->copy_fn) 85 | { 86 | memcpy((void *)catch_var, (void *)catchable->offset.apply((uintptr_t)exception_object), catchable->size); 87 | } 88 | else if (catchable->properties.has_any_of(cxx_catchable_property::has_virtual_base)) 89 | { 90 | using copy_fn_vb_t = void(void * self, void * other, int is_most_derived); 91 | copy_fn_vb_t * fn = (copy_fn_vb_t *)(image_base + catchable->copy_fn); 92 | fn(catch_var, exception_object, 1); 93 | } 94 | else 95 | { 96 | using copy_fn_t = void(void * self, void * other); 97 | copy_fn_t * fn = (copy_fn_t *)(image_base + catchable->copy_fn); 98 | fn(catch_var, exception_object); 99 | } 100 | } 101 | 102 | return true; 103 | } 104 | } 105 | } 106 | 107 | return false; 108 | } 109 | 110 | extern "C" void __cxx_destroy_exception(cxx_catch_info_t & ci) noexcept 111 | { 112 | if (ci.throw_info_if_owner && ci.throw_info_if_owner->destroy_exc_obj) 113 | (__ImageBase + ci.throw_info_if_owner->destroy_exc_obj)(ci.exception_object_or_link); 114 | } 115 | 116 | static pe_unwind_info const * _execute_handler( 117 | cxx_dispatcher_context_t & ctx, 118 | frame_walk_context_t & cpu_ctx, 119 | mach_frame_t & mach) 120 | { 121 | frame_walk_pdata_t const & pdata = *ctx.pdata; 122 | byte const * image_base = pdata.image_base(); 123 | 124 | ctx.fnent = pdata.find_function_entry(mach.rip); 125 | pe_unwind_info const * unwind_info = image_base + ctx.fnent->unwind_info; 126 | 127 | if (unwind_info->flags & 3) 128 | { 129 | size_t unwind_slots = ((size_t)unwind_info->unwind_code_count + 1) & ~(size_t)1; 130 | 131 | auto frame_handler = image_base 132 | + *(rva const *)&unwind_info->data[unwind_slots]; 133 | verify(frame_handler); 134 | 135 | ctx.extra_data = &unwind_info->data[unwind_slots + 2]; 136 | 137 | byte * frame_ptr = reinterpret_cast( 138 | unwind_info->frame_reg? cpu_ctx.gp(unwind_info->frame_reg): mach.rsp); 139 | verify( 140 | frame_handler(nullptr, frame_ptr, nullptr, &ctx) 141 | == win32_exception_disposition::cxx_handler); 142 | } 143 | 144 | return unwind_info; 145 | } 146 | 147 | extern "C" byte const * __cxx_dispatch_exception(void * exception_object, cxx_throw_info const * throw_info, 148 | cxx_throw_frame_t & tf) noexcept 149 | { 150 | frame_walk_pdata_t pdata = frame_walk_pdata_t::for_this_image(); 151 | 152 | cxx_dispatcher_context_t ctx; 153 | ctx.cookie = &unwind_cookie; 154 | ctx.throw_frame = &tf; 155 | ctx.pdata = &pdata; 156 | ctx.handler = nullptr; 157 | 158 | auto & cpu_ctx = tf.ctx; 159 | auto & mach = tf.mach; 160 | auto & ci = tf.catch_info; 161 | 162 | ci.exception_object_or_link = exception_object; 163 | ci.throw_info_if_owner = throw_info; 164 | ci.primary_frame_ptr = 0; 165 | 166 | for (;;) 167 | { 168 | pe_unwind_info const * unwind_info = _execute_handler(ctx, cpu_ctx, mach); 169 | if (ctx.handler) 170 | return ctx.handler; 171 | pdata.unwind(*unwind_info, cpu_ctx, mach); 172 | } 173 | } 174 | 175 | void probe_for_exception_object( 176 | frame_walk_pdata_t const & pdata, 177 | cxx_throw_frame_t & frame) 178 | { 179 | cxx_dispatcher_context_t ctx; 180 | ctx.cookie = &rethrow_probe_cookie; 181 | ctx.throw_frame = &frame; 182 | ctx.pdata = &pdata; 183 | ctx.extra_data = nullptr; 184 | 185 | frame_walk_context_t probe_ctx = frame.ctx; 186 | mach_frame_t probe_mach = frame.mach; 187 | cxx_catch_info_t & catch_info = frame.catch_info; 188 | 189 | for (;;) 190 | { 191 | pe_unwind_info const * unwind_info = _execute_handler(ctx, probe_ctx, probe_mach); 192 | if (catch_info.exception_object_or_link) 193 | break; 194 | pdata.unwind(*unwind_info, probe_ctx, probe_mach); 195 | } 196 | } 197 | 198 | } 199 | 200 | extern "C" x64_frame_handler_t __cxx_seh_frame_handler; 201 | extern "C" win32_exception_disposition __cxx_seh_frame_handler( 202 | win32_exception_record * exception_record, 203 | byte * _frame_ptr, 204 | x64_cpu_context * _cpu_ctx, 205 | void * _ctx 206 | ) 207 | { 208 | (void)_frame_ptr; 209 | (void)_cpu_ctx; 210 | (void)_ctx; 211 | if (exception_record) 212 | verify(exception_record->flags.has_any_of(win32_exception_flag::unwinding)); 213 | 214 | return win32_exception_disposition::continue_search; 215 | } 216 | 217 | extern "C" x64_frame_handler_t __cxx_call_catch_frame_handler; 218 | extern "C" win32_exception_disposition __cxx_call_catch_frame_handler( 219 | win32_exception_record * exception_record, 220 | byte * frame_ptr, 221 | x64_cpu_context * _cpu_ctx, 222 | void * dispatcher_context 223 | ) 224 | { 225 | (void)_cpu_ctx; 226 | if (exception_record) 227 | { 228 | verify(exception_record->flags.has_any_of(win32_exception_flag::unwinding)); 229 | return win32_exception_disposition::continue_search; 230 | } 231 | 232 | cxx_dispatcher_context_t * ctx = static_cast(dispatcher_context); 233 | cxx_throw_frame_t * throw_frame = ctx->throw_frame; 234 | _catch_frame_t * frame = (_catch_frame_t *)frame_ptr; 235 | cxx_catch_info_t & ci = throw_frame->catch_info; 236 | 237 | if (ctx->cookie == &rethrow_probe_cookie) 238 | { 239 | verify(frame->catch_info.exception_object_or_link); 240 | 241 | if (frame->catch_info.throw_info_if_owner) 242 | ci.exception_object_or_link = &frame->catch_info; 243 | else 244 | ci.exception_object_or_link = frame->catch_info.exception_object_or_link; 245 | } 246 | else if (ctx->cookie == &unwind_cookie) 247 | { 248 | if (!ci.exception_object_or_link || ci.exception_object_or_link == &frame->catch_info) 249 | { 250 | ci.exception_object_or_link = frame->catch_info.exception_object_or_link; 251 | ci.throw_info_if_owner = frame->catch_info.throw_info_if_owner; 252 | } 253 | else 254 | { 255 | __cxx_destroy_exception(frame->catch_info); 256 | } 257 | 258 | ci.primary_frame_ptr = frame->catch_info.primary_frame_ptr; 259 | ci.unwind_context = frame->catch_info.unwind_context; 260 | } 261 | else 262 | { 263 | return win32_exception_disposition::continue_search; 264 | } 265 | 266 | return win32_exception_disposition::cxx_handler; 267 | } 268 | -------------------------------------------------------------------------------- /src/x64/throw.h: -------------------------------------------------------------------------------- 1 | #include "cpu_context.h" 2 | #include "../utils.h" 3 | #include "eh_structs_x64.h" 4 | 5 | namespace vcrtl::_msvc::x64 { 6 | 7 | struct cxx_catch_info_t 8 | { 9 | byte const * continuation_address[2]; 10 | byte * primary_frame_ptr; 11 | void * exception_object_or_link; 12 | cxx_throw_info const * throw_info_if_owner; 13 | bytes unwind_context; 14 | 15 | void * get_exception_object() const 16 | { 17 | if (this->throw_info_if_owner) 18 | { 19 | return this->exception_object_or_link; 20 | } 21 | else if (this->exception_object_or_link) 22 | { 23 | cxx_catch_info_t const * other = (cxx_catch_info_t const *)this->exception_object_or_link; 24 | return other->exception_object_or_link; 25 | } 26 | else 27 | { 28 | return nullptr; 29 | } 30 | } 31 | 32 | cxx_throw_info const * get_throw_info() const 33 | { 34 | if (this->exception_object_or_link) 35 | { 36 | if (!this->throw_info_if_owner) 37 | { 38 | cxx_catch_info_t const * other = (cxx_catch_info_t const *)this->exception_object_or_link; 39 | return other->throw_info_if_owner; 40 | } 41 | 42 | return this->throw_info_if_owner; 43 | } 44 | else 45 | { 46 | return nullptr; 47 | } 48 | } 49 | }; 50 | 51 | struct cxx_throw_frame_t 52 | { 53 | uint64_t red_zone[4]; 54 | 55 | frame_walk_context_t ctx; 56 | mach_frame_t mach; 57 | cxx_catch_info_t catch_info; 58 | }; 59 | 60 | void probe_for_exception_object(frame_walk_pdata_t const & pdata, cxx_throw_frame_t & frame); 61 | 62 | bool process_catch_block(byte const * image_base, flags adjectives, 63 | type_info const * match_type, void * catch_var, void * exception_object, cxx_throw_info const & throw_info); 64 | 65 | extern symbol unwind_cookie; 66 | extern symbol rethrow_probe_cookie; 67 | 68 | struct cxx_dispatcher_context_t 69 | { 70 | symbol * cookie; 71 | cxx_throw_frame_t * throw_frame; 72 | frame_walk_pdata_t const * pdata; 73 | pe_function const * fnent; 74 | void const * extra_data; 75 | byte const * handler; 76 | }; 77 | 78 | } 79 | 80 | #pragma once 81 | -------------------------------------------------------------------------------- /src/x64/unwind_handler.cpp: -------------------------------------------------------------------------------- 1 | #include "cpu_context.h" 2 | #include "../bugcheck.h" 3 | #include "../assert.h" 4 | #include "../stdint.h" 5 | using namespace vcrtl; 6 | using namespace vcrtl::_msvc::x64; 7 | 8 | extern "C" symbol __ImageBase; 9 | 10 | namespace { 11 | 12 | template 13 | struct _pe_dir_hdr 14 | { 15 | /* 0x00 */ rva rva; 16 | /* 0x04 */ uint32_t size; 17 | /* 0x08 */ 18 | }; 19 | 20 | 21 | struct _pe64_header 22 | { 23 | /* 0x00 */ uint32_t pe_magic; 24 | 25 | /* 0x04 */ uint16_t machine; 26 | /* 0x06 */ uint8_t _06[0x12]; 27 | 28 | /* 0x18 */ uint16_t opt_magic; 29 | /* 0x1a */ uint8_t _20[0x36]; 30 | /* 0x50 */ uint32_t image_size; 31 | /* 0x54 */ uint32_t headers_size; 32 | /* 0x58 */ uint8_t _58[0x2c]; 33 | /* 0x84 */ uint32_t directory_count; 34 | 35 | /* 0x88 */ _pe_dir_hdr export_table; 36 | /* 0x90 */ _pe_dir_hdr import_table; 37 | /* 0x98 */ _pe_dir_hdr resource_table; 38 | /* 0xa0 */ _pe_dir_hdr exception_table; 39 | }; 40 | 41 | struct _dos_exe_header 42 | { 43 | /* 0x00 */ uint16_t magic; 44 | /* 0x02 */ uint8_t _02[0x3a]; 45 | /* 0x3c */ rva<_pe64_header> pe_header; 46 | /* 0x40 */ 47 | }; 48 | 49 | } 50 | 51 | //frame_info_t unwind_one(); 52 | 53 | 54 | frame_walk_pdata_t frame_walk_pdata_t::for_this_image() 55 | { 56 | return frame_walk_pdata_t(__ImageBase); 57 | } 58 | 59 | frame_walk_pdata_t frame_walk_pdata_t::from_image_base(byte const * image_base) 60 | { 61 | return frame_walk_pdata_t(image_base); 62 | } 63 | 64 | byte const * frame_walk_pdata_t::image_base() const 65 | { 66 | return _image_base; 67 | } 68 | 69 | bool frame_walk_pdata_t::contains_address(byte const * addr) const 70 | { 71 | return _image_base <= addr && addr - _image_base < _image_size; 72 | } 73 | 74 | pe_function const * frame_walk_pdata_t::find_function_entry(byte const * addr) const 75 | { 76 | verify(this->contains_address(addr)); 77 | rva pc_rva = make_rva(addr, _image_base); 78 | 79 | uint32_t l = 0; 80 | uint32_t r = _function_count; 81 | while (l < r) 82 | { 83 | uint32_t i = l + (r - l) / 2; 84 | pe_function const & fn = _functions[i]; 85 | 86 | if (pc_rva < fn.begin) 87 | r = i; 88 | else if (fn.end <= pc_rva) 89 | l = i + 1; 90 | else 91 | return &fn; 92 | } 93 | 94 | return nullptr; 95 | } 96 | 97 | frame_walk_pdata_t::frame_walk_pdata_t(byte const * image_base) 98 | : _image_base(image_base) 99 | { 100 | _dos_exe_header const * dos_hdr = (_dos_exe_header const *)image_base; 101 | verify(dos_hdr->magic == 0x5a4d); 102 | 103 | _pe64_header const * pe_hdr = image_base + dos_hdr->pe_header; 104 | verify(pe_hdr->pe_magic == 0x4550); 105 | verify(pe_hdr->machine == 0x8664); 106 | verify(pe_hdr->opt_magic == 0x20b); 107 | verify(pe_hdr->headers_size >= dos_hdr->pe_header.value() + sizeof(_pe64_header)); 108 | verify(pe_hdr->image_size >= pe_hdr->headers_size); 109 | 110 | verify(pe_hdr->directory_count >= 4); 111 | verify((pe_hdr->exception_table.size % sizeof(pe_function)) == 0); 112 | 113 | _functions = image_base + pe_hdr->exception_table.rva; 114 | _function_count = pe_hdr->exception_table.size / sizeof(pe_function); 115 | _image_size = pe_hdr->image_size; 116 | } 117 | 118 | uint64_t & frame_walk_context_t::gp(uint8_t idx) 119 | { 120 | int8_t conv[16] = { 121 | -1, -1, -1, 0, -1, 1, 2, 3, 122 | -1, -1, -1, -1, 4, 5, 6, 7, 123 | }; 124 | 125 | int8_t offs = conv[idx]; 126 | if (offs < 0) 127 | on_bug_check(bug_check_reason::corrupted_eh_unwind_data); 128 | 129 | return (&this->rbx)[offs]; 130 | }; 131 | 132 | static xmm_t & _xmm(frame_walk_context_t & ctx, uint8_t idx) 133 | { 134 | if (idx < 6 || idx >= 16) 135 | on_bug_check(bug_check_reason::corrupted_eh_unwind_data); 136 | 137 | return (&ctx.xmm6)[idx - 6]; 138 | } 139 | 140 | void frame_walk_pdata_t::unwind(pe_unwind_info const & unwind_info, frame_walk_context_t & ctx, mach_frame_t & mach) const 141 | { 142 | bool rip_updated = false; 143 | for (uint32_t i = 0; i != unwind_info.unwind_code_count; ++i) 144 | { 145 | auto entry = unwind_info.entries[i]; 146 | switch (entry.code) 147 | { 148 | case unwind_code_t::push_nonvolatile_reg: 149 | ctx.gp(entry.info) = *(uint64_t const *)mach.rsp; 150 | mach.rsp += 8; 151 | break; 152 | 153 | case unwind_code_t::alloc_large: 154 | if (entry.info == 0) 155 | { 156 | mach.rsp += unwind_info.data[++i] * 8; 157 | } 158 | else 159 | { 160 | mach.rsp += (uint32_t const &)unwind_info.data[i+1]; 161 | i += 2; 162 | } 163 | break; 164 | 165 | case unwind_code_t::alloc_small: 166 | mach.rsp += entry.info * 8 + 8; 167 | break; 168 | 169 | case unwind_code_t::set_frame_pointer: 170 | mach.rsp = ctx.gp(unwind_info.frame_reg) - unwind_info.frame_reg_disp * 16; 171 | break; 172 | 173 | case unwind_code_t::save_nonvolatile_reg: 174 | ctx.gp(entry.info) = *(uint64_t const *)(mach.rsp + unwind_info.data[++i] * 8); 175 | break; 176 | 177 | case unwind_code_t::save_nonvolatile_reg_far: 178 | ctx.gp(entry.info) = *(uint64_t const *)(mach.rsp + *(uint32_t const *)&unwind_info.data[i]); 179 | i += 2; 180 | break; 181 | 182 | case unwind_code_t::epilog: 183 | i += 1; 184 | break; 185 | 186 | case unwind_code_t::_07: 187 | i += 2; 188 | break; 189 | 190 | case unwind_code_t::save_xmm128: 191 | _xmm(ctx, entry.info) = *(xmm_t const *)(mach.rsp + unwind_info.data[++i] * 16); 192 | break; 193 | 194 | case unwind_code_t::save_xmm128_far: 195 | _xmm(ctx, entry.info) = *(xmm_t const *)(mach.rsp + (uint32_t const &)unwind_info.data[i+1]); 196 | i += 2; 197 | break; 198 | 199 | case unwind_code_t::push_machine_frame: 200 | mach.rsp += (uint64_t)entry.info * 8; 201 | mach.rip = *(byte const **)mach.rsp; 202 | mach.rsp = *(uint64_t const *)(mach.rsp + 24); 203 | rip_updated = true; 204 | break; 205 | } 206 | } 207 | 208 | if (!rip_updated) 209 | { 210 | mach.rip = *(byte const **)mach.rsp; 211 | mach.rsp += 8; 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/x86/README.md: -------------------------------------------------------------------------------- 1 | # C++ Exception Handling on Intel x86 2 | 3 | Here we implement `_CxxThrowException` and `___CxxFrameHandler3` for 4 | the x86 platform. These functions are implicitly referenced by MSVC 5 | when C++ exception handling is used. 6 | 7 | The former function is called whenever the C++ throw statement is executed. 8 | It has the following signature. 9 | 10 | void __stdcall _CxxThrowException(void * exception_object, cxx_throw_info_t const * throw_info); 11 | 12 | If a new exception object is to be thrown, it is allocated on the thrower's 13 | stack frame, then `_CxxThrowException` is called with the pointer to the 14 | exception object as the first argument. The second argument is a pointer 15 | to a read-only structure describing the exception object's type. It contains, 16 | in particular, 17 | 18 | * the pointer to a function responsible for destroying the exception object, and 19 | * the list of _catchables_, each of which specifies one of the types that 20 | can appear in a matching catch handler, along with information needed to 21 | initialize the catch variable. 22 | 23 | If an existing exception is to be rethrown, both arguments are `nullptr`. 24 | 25 | The function is supposed to walk the stack frames until a matching catch 26 | handler is found, destroying the visited stack frames in the process. 27 | 28 | To understand `___CxxFrameHandler3`, a short introduction to [SEH][1] is needed. 29 | 30 | ## Structured Exception Handling 31 | 32 | 33 | 34 | ## Calling conventions 35 | 36 | The peculiarity of the x86 platform is that multiple competing calling 37 | conventions have evolved over time. Most of the time, the convention need not 38 | be specified explicitly, the compiler will invoke routines as it sees fit. 39 | 40 | For functions with external linkage, the calling convention is selected 41 | by compiler options and defaults to cdecl. For the rest, the compiler 42 | can be quite creative and invents its own conventions. 43 | 44 | The functions for which the convention must to be specified explicitly include 45 | 46 | * declarations Windows API functions, 47 | * the functions called back by Windows (like frame handlers), and 48 | * functions written in assembly. 49 | 50 | For the latter, we use fastcall, as it is best in all aspects. We also humbly 51 | suggest that you set it as your [default calling convention][1]. 52 | 53 | All standard calling conventions agree that `ebx`, `esi`, `edi` and `ebp` 54 | are non-volatile (aka. callee-saved) and that results are returned in `eax`. 55 | 56 | Some compiler-called routines, including `___CxxFrameHandler3`, and 57 | unwind funclets, use a custom calling convention. 58 | 59 | ### cdecl 60 | 61 | This is the default calling convention for the MSVC compiler, unless you use 62 | change the default with a compiler option. It is also the only 63 | calling convention that supports variable number of arguments; the compiler 64 | will fall back to cdecl for vararg functions. 65 | 66 | Arguments are pushed onto the stack in right-to-left order and are popped 67 | by the caller. This differs from other calling conventions, but allows 68 | the callee to be unaware of the parameter count, thus allowing varargs. 69 | 70 | `extern "C"` cdecl function names are mangled by prefixing them with 71 | an underscore. 72 | 73 | Ironically, frame handlers use cdecl convention, but `_CxxThrowException` 74 | uses stdcall. 75 | 76 | ### stdcall 77 | 78 | Unlike cdecl, the callee is responsible for cleaning up the stack, which leads 79 | to smaller executables in overall (fewer cleanup sequences in generated code), 80 | but prohibits varargs. 81 | 82 | When mangled, `extern "C"` functions are prefixed by an underscore and suffixed 83 | by `@` followed by a decimal number specifying the amount of stack the function 84 | frees. This is actually an ingenious way to prevent stack corruptions due to 85 | ABI changes. 86 | 87 | Note that stdcall is superior to cdecl for non-vararg functions. 88 | 89 | `_CxxThrowException` uses stdcall and is therefore mangled 90 | to `__CxxThrowException@8`. 91 | 92 | [1]: https://docs.microsoft.com/en-us/cpp/build/reference/gd-gr-gv-gz-calling-convention 93 | 94 | ### fastcall 95 | 96 | In fastcall, the first two arguments are passed in `ecx` and `edx` respectively. 97 | The rest are passed as in stdcall. This makes the code faster and with smaller 98 | footprint. 99 | 100 | ### thiscall 101 | 102 | By default, member functions use thiscall, where the implicit `this` argument 103 | is passed in `ecx`. Otherwise, thiscall is the same as stdcall. 104 | 105 | Unfortunately, you can't form a pointer to a thiscall function with MSVC. 106 | However, you can work around this problem by declaring your function pointer 107 | as fastcall and adding a dummy `int` parameter. For example, you can call 108 | a copy constructor through a pointer with this signature. 109 | 110 | void (__fastcall * fn)(void * this_, int dummy_, void * other); 111 | -------------------------------------------------------------------------------- /src/x86/eh_helpers.asm: -------------------------------------------------------------------------------- 1 | .model flat 2 | assume fs:nothing 3 | 4 | public __EH_prolog3 5 | public __EH_prolog3_catch 6 | public __EH_epilog3 7 | 8 | extern ___security_cookie: dword 9 | 10 | .code 11 | 12 | __EH_prolog3_catch proc 13 | __EH_prolog3:: 14 | ; This is a special helper routine that is called at the beginning 15 | ; of a function when optimizing for size. The caller's prolog might look 16 | ; like this. 17 | ; 18 | ; push 8 19 | ; mov eax, 6C2CB4h 20 | ; call _EH_prolog3_catch 21 | ; 22 | ; In `eax`, we receive the address of the caller's frame handler, which 23 | ; we are expected to register. Upon entry, the stack looks as follows. 24 | ; 25 | ; esp : our return address 26 | ; esp+04h: the size of the stack allocation 27 | ; esp+08h: caller's return address 28 | ; 29 | ; The caller expects us to setup its FH3 stack frame, allocate space 30 | ; for its local variables, save nonvolatile registers, and store 31 | ; the frame cookie at the top of the frame. 32 | ; 33 | ; We must not touch `ecx` and `edx`, since they may contain caller's 34 | ; parameters. This leave `eax` as our only volatile register. 35 | ; 36 | ; Upon return, the stack should look like this. 37 | ; 38 | ; esp : frame cookie 39 | ; esp+04h: saved edi 40 | ; esp+08h: saved esi 41 | ; esp+0ch: saved ebx 42 | ; esp+10h: stack allocation 43 | ; ... : 44 | ; ebp-10h: frame limit (i.e. esp after return) 45 | ; ebp-0ch: next frame handler registration 46 | ; ebp-08h: frame handler 47 | ; ebp-04h: 0xffffffff (current FH3 function state) 48 | ; ebp : saved ebp 49 | ; ebp+04h: caller's return address 50 | 51 | push eax 52 | push dword ptr fs:[0] 53 | 54 | ; esp : next frame handler registration 55 | ; esp+04h: frame handler 56 | ; esp+08h: our return address 57 | ; esp+0ch: the size of the stack allocation 58 | ; esp+10h: caller's return address 59 | 60 | mov eax, [esp+0ch] 61 | mov [esp+0ch], ebp 62 | lea ebp, [esp+0ch] 63 | 64 | ; At this point the frame looks this way. 65 | ; 66 | ; ebp-0ch: next frame handler registration 67 | ; ebp-08h: frame handler 68 | ; ebp-04h: our return address 69 | ; ebp : saved ebp 70 | ; ebp+04h: caller's return address 71 | ; 72 | ; The stack allocation size is now in `eax`. We now extend the stack 73 | ; to include the stack allocation, which frees `eax` for further use. 74 | 75 | sub esp, 4 76 | sub esp, eax 77 | 78 | ; Save non-volatile registers. 79 | 80 | push ebx 81 | push esi 82 | push edi 83 | 84 | ; Setup the frame cookie. 85 | 86 | mov eax, [___security_cookie] 87 | xor eax, ebp 88 | push eax 89 | 90 | mov [ebp-10h], esp 91 | 92 | push dword ptr [ebp-04h] 93 | mov dword ptr [ebp-04h], -1 94 | 95 | lea eax, [ebp+0ch] 96 | mov fs:[0], eax 97 | 98 | ret 99 | __EH_prolog3_catch endp 100 | 101 | __EH_epilog3 proc 102 | ; This is called right before `ret` to tear down a stack frame. 103 | ; We are supposed to unregister the frame handler and restore 104 | ; non-volatile registers to pre-prolog state (including `ebp` and `esp`). 105 | ; 106 | ; We must not touch `eax` as it contains the caller's return value. 107 | 108 | mov ecx, [ebp-0ch] 109 | mov fs:[0], ecx 110 | 111 | pop ecx 112 | add esp, 4 113 | pop edi 114 | pop esi 115 | pop ebx 116 | 117 | mov esp, ebp 118 | pop ebp 119 | 120 | jmp ecx 121 | __EH_epilog3 endp 122 | 123 | end 124 | -------------------------------------------------------------------------------- /src/x86/eh_structs_x86.h: -------------------------------------------------------------------------------- 1 | #include "../ptr_to_member.h" 2 | #include "../flags.h" 3 | #include "../stdint.h" 4 | 5 | namespace vcrtl::_msvc::x86 { 6 | 7 | enum class cxx_throw_flag: uint32_t 8 | { 9 | is_const = 0x01, 10 | is_volatile = 0x02, 11 | is_unaligned = 0x04, 12 | is_pure = 0x08, 13 | is_winrt = 0x10, 14 | }; 15 | 16 | enum class cxx_catchable_property: uint32_t 17 | { 18 | is_simple_type = 0x01, 19 | by_reference_only = 0x02, 20 | has_virtual_base = 0x04, 21 | is_winrt_handle = 0x08, 22 | is_std_bad_alloc = 0x10, 23 | }; 24 | 25 | struct cxx_catchable_type 26 | { 27 | flags properties; 28 | type_info const * desc; 29 | cxx_ptr_to_member offset; 30 | uint32_t size; 31 | uint32_t copy_fn_rva; 32 | }; 33 | 34 | struct cxx_catchable_type_list 35 | { 36 | uint32_t count; 37 | #pragma warning(suppress: 4200) 38 | cxx_catchable_type const * types[]; 39 | }; 40 | 41 | struct throw_info_t 42 | { 43 | flags attributes; 44 | void (__fastcall * destroy_exc_obj)(void *); 45 | int (__cdecl * compat_fn)(...); 46 | cxx_catchable_type_list const * catchables; 47 | }; 48 | 49 | struct cxx_eh_funclet; 50 | 51 | struct cxx_unwind_graph_edge 52 | { 53 | int32_t next; 54 | cxx_eh_funclet * cleanup_handler; 55 | }; 56 | 57 | enum class cxx_catch_flag : uint32_t 58 | { 59 | is_const = 1, 60 | is_volatile = 2, 61 | is_unaligned = 4, 62 | is_reference = 8, 63 | is_resumable = 0x10, 64 | is_ellipsis = 0x40, 65 | is_bad_alloc_compat = 0x80, 66 | is_complus_eh = 0x80000000, 67 | }; 68 | 69 | struct cxx_catch_handler 70 | { 71 | flags adjectives; 72 | type_info const * type_desc; 73 | int32_t catch_object_offset; 74 | cxx_eh_funclet * handler; 75 | }; 76 | 77 | struct cxx_try_block 78 | { 79 | /* 0x00 */ int32_t try_low; 80 | /* 0x04 */ int32_t try_high; 81 | /* 0x08 */ int32_t catch_high; 82 | /* 0x0c */ int32_t catch_count; 83 | /* 0x10 */ cxx_catch_handler const * catch_handlers; 84 | }; 85 | 86 | enum class cxx_eh_flag : uint32_t 87 | { 88 | compiled_with_ehs = 1, 89 | is_noexcept = 4, 90 | }; 91 | 92 | struct cxx_function_x86_eh_info 93 | { 94 | /* 0x00 */ uint32_t _magic; 95 | /* 0x04 */ uint32_t state_count; 96 | /* 0x08 */ cxx_unwind_graph_edge const * unwind_graph; 97 | /* 0x0c */ int32_t try_block_count; 98 | /* 0x10 */ cxx_try_block const * try_blocks; 99 | /* 0x14 */ uint32_t _14; 100 | /* 0x18 */ uint32_t _18; 101 | 102 | // `_magic >= 0x19930521` 103 | /* 0x1c */ uint32_t es_types; 104 | 105 | // `_magic >= 0x19930522` 106 | /* 0x20 */ flags eh_flags; 107 | /* 0x24 */ 108 | }; 109 | 110 | } 111 | 112 | #pragma once 113 | -------------------------------------------------------------------------------- /src/x86/fh3_x86.asm: -------------------------------------------------------------------------------- 1 | .model flat 2 | assume fs:nothing 3 | 4 | public __CxxThrowException@8 5 | public ___CxxFrameHandler3 6 | public @__cxx_call_funclet@8 7 | 8 | public __NLG_Return2 9 | 10 | extern _NLG_Notify: proc 11 | extern ___fh3_primary_handler: proc 12 | extern @__cxx_destroy_throw_frame@4: proc 13 | extern @__cxx_dispatch_exception@4: proc 14 | extern ___cxx_catch_frame_handler: proc 15 | 16 | .safeseh ___fh3_primary_handler 17 | .safeseh ___cxx_catch_frame_handler 18 | 19 | fh3_fr struct 20 | frame_limit dword ? 21 | 22 | next_frame_reg dword ? 23 | frame_handler dword ? 24 | 25 | state dword ? 26 | next_frame_base dword ? 27 | fh3_fr ends 28 | 29 | .code 30 | 31 | __CxxThrowException@8 proc 32 | ; This function is called implicitly by the compiler whenever a throw 33 | ; statement is executed. It receives two arguments, 34 | ; 35 | ; * the pointer to the exception object, and 36 | ; * the pointer to the `cxx_throw_info_t` structure corresponding to 37 | ; the type of the exception object. 38 | ; 39 | ; We take ownership of the exception object here and are responsible for 40 | ; destroying it. The pointer to the destructor routine is in the throw info. 41 | ; 42 | ; If the throw is a rethrow, both of the arguments are `nullptr`. 43 | ; 44 | ; This function should never return, instead, it should 45 | ; 46 | ; * locate the closest function call frame with a catch handler that matches 47 | ; the type of the exception object, 48 | ; * unwind all frames above it, 49 | ; * partially unwind the target frame so that the local variables in the 50 | ; try block that caught the exception are destroyed, 51 | ; * copy construct the catch variable from the exception object, or if 52 | ; the variable is of reference type, bind it to the exception object, 53 | ; * call the catch handler funclet, 54 | ; * destroy the exception object, and 55 | ; * resume execution at the address returned by the catch handler funclet. 56 | ; 57 | ; In the process, a nested C++ exception can be thrown from one of the 58 | ; following places: 59 | ; 60 | ; 1. the destructor of a local variable during the unwind, 61 | ; 2. the copy constructor of the catch variable, 62 | ; 3. the catch handler funclet, or 63 | ; 4. the destructor of the exception object. 64 | ; 65 | ; If a nested exception is thrown, we must ensure that the destruction 66 | ; of local variables and of this frame's exception object are properly 67 | ; sequenced. In particular, the order of destruction should be as follows. 68 | ; 69 | ; 1. Local variables in the catch handler funclet should be destroyed, then 70 | ; 2. the exception object owned by this frame, and then 71 | ; 3. the rest of the local variables. 72 | ; 73 | ; Note that the catch handler funclet doesn't register a frame handler, 74 | ; which means that we must register one and take care of unwinding 75 | ; both the funclet's frame and our own. As such, our frame must contain 76 | ; all the information needed to perfom the unwind: 77 | ; 78 | ; * the pointer to the base of the function's frame, 79 | ; * the function's unwind graph, accessible from the function's eh info, and 80 | ; * the target state to which the frame should be unwound. 81 | 82 | throw_fr struct 83 | eh_info dword ? 84 | unwound_state dword ? 85 | primary_frame_base dword ? 86 | 87 | exception_object dword ? 88 | throw_info dword ? 89 | throw_fr ends 90 | 91 | ; Here we construct the throw frame, fill in the exception object pointer 92 | ; and the throw info. We also register `__cxx_throw_frame_handler` 93 | ; as our frame handler. 94 | 95 | push ebp 96 | mov ebp, esp 97 | 98 | push [ebp+0ch] 99 | push [ebp+8] 100 | 101 | push 0 102 | push 0 103 | push 0 104 | 105 | ; Our frame is setup, we now call `__cxx_dispatch_exception`, which 106 | ; takes our frame, the _throw frame_, as its only argument. 107 | ; This call is responsible for most of the work: it unwinds the frames, 108 | ; locates the appropriate catch handler, and constructs its catch 109 | ; variable. It also stores the catch handler's unwind info into 110 | ; the throw frame so that we can properly handle nested exceptions. 111 | ; It then returns the catch handler funclet's address. 112 | ; 113 | ; The function may throw a nested C++ exception from the catch variable's 114 | ; constructor, in which case we destroy the exception object and 115 | ; let the exception propagate. The unwind itself should not leak any 116 | ; exceptions, `std::terminate` should be called instead. 117 | 118 | lea ecx, [esp] 119 | call @__cxx_dispatch_exception@4 120 | 121 | ; The catch handler's address is now in `eax`. The funclet doesn't 122 | ; expect any arguments, but expects to run in the primary funclet's frame. 123 | ; Here we update the `ebp`. 124 | 125 | mov ebp, [esp + throw_fr.primary_frame_base] 126 | 127 | push 100h 128 | call _NLG_Notify 129 | 130 | ; The following call will not respect non-volatility of registers, 131 | ; we can only assume that `ebp` and `esp` will survive. 132 | ; 133 | ; The handler may throw. 134 | 135 | push offset ___cxx_catch_frame_handler 136 | push fs:[0] 137 | mov fs:[0], esp 138 | 139 | call eax 140 | __NLG_Return2:: 141 | mov ebx, eax 142 | 143 | pop ecx 144 | mov fs:[0], ecx 145 | 146 | mov ecx, 2 147 | mov [esp], ecx 148 | call _NLG_Notify 149 | 150 | ; From this point on, nothing may throw. If an exception leaks from 151 | ; the exception object's destructor, we bug check before the exception 152 | ; reaches us. We therefore unregister the frame handler. 153 | 154 | lea ecx, [esp] 155 | call @__cxx_destroy_throw_frame@4 156 | 157 | mov esp, [ebp - fh3_fr.next_frame_base + fh3_fr.frame_limit] 158 | jmp ebx 159 | __CxxThrowException@8 endp 160 | 161 | @__cxx_call_funclet@8 proc 162 | push ebp 163 | mov ebp, edx 164 | call ecx 165 | pop ebp 166 | ret 167 | @__cxx_call_funclet@8 endp 168 | 169 | ___CxxFrameHandler3 proc 170 | mov [esp+0ch], eax 171 | jmp ___fh3_primary_handler 172 | ___CxxFrameHandler3 endp 173 | 174 | end 175 | -------------------------------------------------------------------------------- /src/x86/memcpy.asm: -------------------------------------------------------------------------------- 1 | .model flat 2 | public _memcpy 3 | 4 | .code 5 | 6 | memcpy_fr struct 7 | ret_addr dword ? 8 | dest dword ? 9 | src dword ? 10 | sz dword ? 11 | memcpy_fr ends 12 | 13 | _memcpy proc 14 | mov eax, esi 15 | mov edx, edi 16 | 17 | mov edi, [esp + memcpy_fr.dest] 18 | mov esi, [esp + memcpy_fr.src] 19 | mov ecx, [esp + memcpy_fr.sz] 20 | rep movsb 21 | 22 | mov edi, edx 23 | mov esi, eax 24 | 25 | mov eax, [esp + memcpy_fr.dest] 26 | ret 27 | _memcpy endp 28 | 29 | end 30 | -------------------------------------------------------------------------------- /src/x86/nlg.asm: -------------------------------------------------------------------------------- 1 | .model flat 2 | .code 3 | 4 | public __NLG_Dispatch 5 | 6 | _NLG_Notify proc public 7 | push ebp 8 | push [esp+8] 9 | push eax 10 | __NLG_Dispatch:: 11 | add esp, 0ch 12 | ret 4 13 | _NLG_Notify endp 14 | 15 | end 16 | -------------------------------------------------------------------------------- /src/x86/throw_x86.cpp: -------------------------------------------------------------------------------- 1 | #include "../intrin.h" 2 | #include "../memcpy.h" 3 | #include "../algorithm.h" 4 | #include "../stdint.h" 5 | #include "eh_structs_x86.h" 6 | #include "../bugcheck.h" 7 | #include "../rva.h" 8 | #include "../win32_seh.h" 9 | 10 | namespace vcrtl::_msvc::x86 { 11 | 12 | static x86_seh_registration * get_seh_head() 13 | { 14 | return (x86_seh_registration *)__readfsdword(0); 15 | } 16 | 17 | #pragma warning(disable: 4733) 18 | 19 | static void set_seh_head(x86_seh_registration * p) 20 | { 21 | __writefsdword(0, (unsigned long)p); 22 | } 23 | 24 | struct fh3_frame_t 25 | { 26 | uintptr_t frame_limit; 27 | x86_seh_registration seh_reg; 28 | int32_t state; 29 | uintptr_t next_frame_base; 30 | }; 31 | 32 | struct cxx_throw_frame_t 33 | { 34 | cxx_function_x86_eh_info const * eh_info; 35 | int32_t unwound_state; 36 | uint32_t primary_frame_base; 37 | 38 | void * exception_object_or_link; 39 | throw_info_t const * throw_info_if_owner; 40 | 41 | throw_info_t const * get_throw_info() const 42 | { 43 | if (throw_info_if_owner) 44 | return throw_info_if_owner; 45 | if (!exception_object_or_link) 46 | return nullptr; 47 | 48 | cxx_throw_frame_t * link = (cxx_throw_frame_t *)exception_object_or_link; 49 | return link->throw_info_if_owner; 50 | } 51 | 52 | void * get_exception_object() 53 | { 54 | if (throw_info_if_owner) 55 | return exception_object_or_link; 56 | 57 | cxx_throw_frame_t * link = (cxx_throw_frame_t *)exception_object_or_link; 58 | return link->exception_object_or_link; 59 | } 60 | }; 61 | 62 | struct cxx_catch_frame_t 63 | { 64 | x86_seh_registration seh_registration; 65 | cxx_throw_frame_t throw_frame; 66 | }; 67 | 68 | 69 | extern "C" { 70 | 71 | extern rva const __safe_se_handler_table[]; 72 | extern symbol __safe_se_handler_count; 73 | extern symbol __ImageBase; 74 | 75 | } 76 | 77 | namespace { 78 | 79 | struct _seh_verifier 80 | { 81 | _seh_verifier() 82 | { 83 | _stack_base = __readfsdword(4); 84 | _stack_limit = __readfsdword(8); 85 | } 86 | 87 | void verify_seh_registration(x86_seh_registration * p) const 88 | { 89 | uint32_t addr = reinterpret_cast(p); 90 | if (addr < _stack_limit || _stack_base <= addr) 91 | on_bug_check(bug_check_reason::corrupted_exception_registration_chain); 92 | 93 | if (!binary_search( 94 | __safe_se_handler_table, __safe_se_handler_table + __safe_se_handler_count, 95 | make_rva(p->handler, __ImageBase))) 96 | { 97 | on_bug_check(bug_check_reason::seh_handler_not_in_safeseh); 98 | } 99 | } 100 | 101 | private: 102 | uint32_t _stack_base; 103 | uint32_t _stack_limit; 104 | }; 105 | 106 | struct _dispatcher_context_t 107 | { 108 | symbol * cookie; 109 | cxx_throw_frame_t * throw_frame; 110 | _seh_verifier const * verifier; 111 | cxx_eh_funclet * handler; 112 | }; 113 | 114 | symbol _unwind_cookie; 115 | symbol _rethrow_probe_cookie; 116 | 117 | } 118 | 119 | static void _probe_for_exception_object(cxx_throw_frame_t & throw_frame, _seh_verifier const & verifier) 120 | { 121 | _dispatcher_context_t ctx; 122 | ctx.cookie = &_rethrow_probe_cookie; 123 | ctx.throw_frame = &throw_frame; 124 | ctx.verifier = &verifier; 125 | 126 | x86_seh_registration * current = get_seh_head(); 127 | while (!throw_frame.exception_object_or_link) 128 | { 129 | verifier.verify_seh_registration(current); 130 | 131 | win32_exception_disposition disposition = current->handler(nullptr, current, nullptr, &ctx); 132 | if (disposition != win32_exception_disposition::cxx_handler) 133 | on_bug_check(bug_check_reason::unwinding_non_cxx_frame); 134 | 135 | current = current->next; 136 | } 137 | } 138 | 139 | 140 | extern "C" void __fastcall __cxx_destroy_throw_frame(cxx_throw_frame_t * throw_frame) noexcept 141 | { 142 | if (throw_frame->throw_info_if_owner && throw_frame->throw_info_if_owner->destroy_exc_obj) 143 | throw_frame->throw_info_if_owner->destroy_exc_obj(throw_frame->exception_object_or_link); 144 | } 145 | 146 | extern "C" uintptr_t __fastcall __cxx_call_funclet(cxx_eh_funclet * funclet, uintptr_t frame_base); 147 | 148 | static void _unwind_frame(fh3_frame_t * frame, cxx_function_x86_eh_info const * eh_info, int32_t target_state) 149 | { 150 | int32_t state = frame->state; 151 | while (state > target_state) 152 | { 153 | cxx_unwind_graph_edge const & edge = eh_info->unwind_graph[state]; 154 | state = edge.next; 155 | frame->state = state; 156 | 157 | if (edge.cleanup_handler) 158 | (void)__cxx_call_funclet(edge.cleanup_handler, (uintptr_t)&frame->next_frame_base); 159 | } 160 | } 161 | 162 | static void _fh3_handler( 163 | fh3_frame_t * frame, cxx_function_x86_eh_info const * eh_info, 164 | int32_t unwound_state, _dispatcher_context_t & ctx) noexcept 165 | { 166 | if (eh_info->_magic >= 0x19930521 && eh_info->es_types) 167 | on_bug_check(bug_check_reason::exception_specification_not_supported); 168 | 169 | cxx_throw_frame_t * throw_frame = ctx.throw_frame; 170 | throw_info_t const * throw_info = throw_frame->get_throw_info(); 171 | uintptr_t frame_base = (uintptr_t)&frame->next_frame_base; 172 | 173 | int32_t state = frame->state; 174 | 175 | for (int32_t i = 0; i < eh_info->try_block_count; ++i) 176 | { 177 | cxx_try_block const & try_block = eh_info->try_blocks[i]; 178 | if (try_block.try_low <= unwound_state) 179 | break; 180 | 181 | if (try_block.try_low > state || state > try_block.try_high) 182 | continue; 183 | 184 | cxx_catch_handler const * catch_handlers = try_block.catch_handlers; 185 | for (int32_t j = 0; j < try_block.catch_count; ++j) 186 | { 187 | cxx_catch_handler const & catch_handler = catch_handlers[j]; 188 | 189 | cxx_catchable_type const * matching_catchable = nullptr; 190 | if (!catch_handler.adjectives.has_any_of(cxx_catch_flag::is_ellipsis)) 191 | { 192 | if (!throw_info) 193 | { 194 | _probe_for_exception_object(*throw_frame, *ctx.verifier); 195 | throw_info = throw_frame->get_throw_info(); 196 | } 197 | 198 | cxx_catchable_type_list const * catchables = throw_info->catchables; 199 | for (uint32_t k = 0; k < catchables->count; ++k) 200 | { 201 | cxx_catchable_type const * catchable = 0 + catchables->types[k]; 202 | if (catch_handler.type_desc == catchable->desc) 203 | { 204 | matching_catchable = catchable; 205 | break; 206 | } 207 | } 208 | 209 | if (!matching_catchable) 210 | continue; 211 | } 212 | 213 | ctx.handler = catch_handler.handler; 214 | throw_frame->primary_frame_base = frame_base; 215 | throw_frame->eh_info = eh_info; 216 | throw_frame->unwound_state = try_block.try_low - 1; 217 | 218 | _unwind_frame(frame, eh_info, try_block.try_low); 219 | frame->state = try_block.try_high + 1; 220 | 221 | if (catch_handler.adjectives.has_any_of(cxx_catch_flag::is_reference)) 222 | { 223 | void *& catch_object = *(void **)(frame_base + catch_handler.catch_object_offset); 224 | catch_object = throw_frame->get_exception_object(); 225 | } 226 | else if (matching_catchable && catch_handler.catch_object_offset) 227 | { 228 | void * exception_object = throw_frame->get_exception_object(); 229 | uintptr_t catch_var = throw_frame->primary_frame_base + catch_handler.catch_object_offset; 230 | 231 | if (matching_catchable->properties.has_any_of(cxx_catchable_property::is_simple_type)) 232 | { 233 | memcpy((void *)catch_var, (void *)exception_object, matching_catchable->size); 234 | if (matching_catchable->size == sizeof(uintptr_t)) 235 | { 236 | uintptr_t & ptr = *(uintptr_t *)catch_var; 237 | if (ptr) 238 | ptr = matching_catchable->offset.apply(ptr); 239 | } 240 | } 241 | else if (matching_catchable->copy_fn_rva == 0) 242 | { 243 | memcpy((void *)catch_var, (void *)matching_catchable->offset.apply((uintptr_t)exception_object), 244 | matching_catchable->size); 245 | } 246 | else if (matching_catchable->properties.has_any_of(cxx_catchable_property::has_virtual_base)) 247 | { 248 | using copy_fn_vb_t = void __fastcall(uintptr_t self, uintptr_t edx, void * other, int is_most_derived); 249 | copy_fn_vb_t * fn = (copy_fn_vb_t *)matching_catchable->copy_fn_rva; 250 | fn(catch_var, reinterpret_cast(fn), exception_object, 1); 251 | } 252 | else 253 | { 254 | using copy_fn_t = void __fastcall (uintptr_t self, uintptr_t edx, void * other); 255 | copy_fn_t * fn = (copy_fn_t *)matching_catchable->copy_fn_rva; 256 | fn(catch_var, reinterpret_cast(fn), exception_object); 257 | } 258 | } 259 | 260 | return; 261 | } 262 | } 263 | 264 | _unwind_frame(frame, eh_info, unwound_state); 265 | 266 | if (state == -1 267 | && eh_info->_magic >= 0x19930522 268 | && eh_info->eh_flags.has_any_of(cxx_eh_flag::is_noexcept) 269 | && !ctx.handler) 270 | { 271 | on_bug_check(bug_check_reason::noexcept_violation); 272 | } 273 | } 274 | 275 | using cxx_frame_handler_t = win32_exception_disposition __cdecl( 276 | win32_exception_record * exception_record, x86_seh_registration * registration_record, 277 | cxx_function_x86_eh_info const * eh_info, _dispatcher_context_t & ctx); 278 | 279 | extern "C" cxx_frame_handler_t __fh3_primary_handler; 280 | extern "C" win32_exception_disposition __cdecl __fh3_primary_handler( 281 | win32_exception_record * exception_record, x86_seh_registration * seh_reg, 282 | cxx_function_x86_eh_info const * eh_info, _dispatcher_context_t & ctx) 283 | { 284 | if (exception_record) 285 | { 286 | if (exception_record->flags.has_any_of(win32_exception_flag::unwinding)) 287 | on_bug_check(bug_check_reason::unwind_on_unsafe_exception); 288 | return win32_exception_disposition::continue_search; 289 | } 290 | 291 | if (eh_info->_magic < 0x19930520 || eh_info->_magic >= 0x19940000) 292 | on_bug_check(bug_check_reason::corrupted_eh_unwind_data); 293 | 294 | if (ctx.cookie == &_unwind_cookie) 295 | { 296 | fh3_frame_t * frame = container_of(seh_reg, fh3_frame_t, seh_reg); 297 | _fh3_handler(frame, eh_info, -1, ctx); 298 | } 299 | else if (ctx.cookie != &_rethrow_probe_cookie) 300 | { 301 | return win32_exception_disposition::continue_search; 302 | } 303 | 304 | return win32_exception_disposition::cxx_handler; 305 | } 306 | 307 | extern "C" x86_frame_handler_t __cxx_catch_frame_handler; 308 | extern "C" win32_exception_disposition __cdecl __cxx_catch_frame_handler( 309 | win32_exception_record * exception_record, x86_seh_registration * seh_reg, 310 | x86_cpu_context * /*cpu_context*/, void * dispatcher_context) 311 | { 312 | if (exception_record) 313 | { 314 | if (exception_record->flags.has_any_of(win32_exception_flag::unwinding)) 315 | on_bug_check(bug_check_reason::unwind_on_unsafe_exception); 316 | return win32_exception_disposition::continue_search; 317 | } 318 | 319 | cxx_catch_frame_t * frame = container_of(seh_reg, cxx_catch_frame_t, seh_registration); 320 | _dispatcher_context_t * ctx = static_cast<_dispatcher_context_t *>(dispatcher_context); 321 | cxx_throw_frame_t * throw_frame = ctx->throw_frame; 322 | 323 | if (ctx->cookie == &_rethrow_probe_cookie) 324 | { 325 | throw_frame->exception_object_or_link = &frame->throw_frame; 326 | return win32_exception_disposition::cxx_handler; 327 | } 328 | 329 | if (ctx->cookie != &_unwind_cookie) 330 | return win32_exception_disposition::continue_search; 331 | 332 | if (throw_frame->exception_object_or_link == &frame->throw_frame) 333 | { 334 | throw_frame->exception_object_or_link = frame->throw_frame.exception_object_or_link; 335 | throw_frame->throw_info_if_owner = frame->throw_frame.throw_info_if_owner; 336 | } 337 | else if (!throw_frame->exception_object_or_link) 338 | { 339 | throw_frame->exception_object_or_link = &frame->throw_frame; 340 | } 341 | 342 | fh3_frame_t * primary_frame = container_of(frame->throw_frame.primary_frame_base, fh3_frame_t, next_frame_base); 343 | _fh3_handler(primary_frame, frame->throw_frame.eh_info, frame->throw_frame.unwound_state, *ctx); 344 | 345 | if (!ctx->handler) 346 | { 347 | if (throw_frame->exception_object_or_link == &frame->throw_frame) 348 | { 349 | throw_frame->exception_object_or_link = frame->throw_frame.exception_object_or_link; 350 | throw_frame->throw_info_if_owner = frame->throw_frame.throw_info_if_owner; 351 | } 352 | else if (frame->throw_frame.throw_info_if_owner && frame->throw_frame.throw_info_if_owner->destroy_exc_obj) 353 | { 354 | frame->throw_frame.throw_info_if_owner->destroy_exc_obj(frame->throw_frame.exception_object_or_link); 355 | } 356 | } 357 | 358 | return win32_exception_disposition::cxx_handler; 359 | } 360 | 361 | 362 | extern "C" cxx_eh_funclet * __fastcall __cxx_dispatch_exception(cxx_throw_frame_t * throw_frame) 363 | { 364 | _seh_verifier verifier; 365 | 366 | _dispatcher_context_t ctx; 367 | ctx.cookie = &_unwind_cookie; 368 | ctx.throw_frame = throw_frame; 369 | ctx.verifier = &verifier; 370 | ctx.handler = nullptr; 371 | 372 | x86_seh_registration * seh_head = get_seh_head(); 373 | for (;;) 374 | { 375 | verifier.verify_seh_registration(seh_head); 376 | 377 | win32_exception_disposition exception_disposition = seh_head->handler(nullptr, seh_head, nullptr, &ctx); 378 | if (exception_disposition != win32_exception_disposition::cxx_handler) 379 | on_bug_check(bug_check_reason::unwinding_non_cxx_frame); 380 | 381 | if (ctx.handler) 382 | return ctx.handler; 383 | 384 | seh_head = seh_head->next; 385 | set_seh_head(seh_head); 386 | } 387 | } 388 | 389 | } 390 | -------------------------------------------------------------------------------- /test/build.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29509.3 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "build_test", "build_test.vcxproj", "{0A90B0A5-939F-4E28-9379-3CDEDA4B5968}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {0A90B0A5-939F-4E28-9379-3CDEDA4B5968}.Debug|x64.ActiveCfg = Debug|x64 17 | {0A90B0A5-939F-4E28-9379-3CDEDA4B5968}.Debug|x64.Build.0 = Debug|x64 18 | {0A90B0A5-939F-4E28-9379-3CDEDA4B5968}.Debug|x64.Deploy.0 = Debug|x64 19 | {0A90B0A5-939F-4E28-9379-3CDEDA4B5968}.Debug|x86.ActiveCfg = Debug|Win32 20 | {0A90B0A5-939F-4E28-9379-3CDEDA4B5968}.Debug|x86.Build.0 = Debug|Win32 21 | {0A90B0A5-939F-4E28-9379-3CDEDA4B5968}.Debug|x86.Deploy.0 = Debug|Win32 22 | {0A90B0A5-939F-4E28-9379-3CDEDA4B5968}.Release|x64.ActiveCfg = Release|x64 23 | {0A90B0A5-939F-4E28-9379-3CDEDA4B5968}.Release|x64.Build.0 = Release|x64 24 | {0A90B0A5-939F-4E28-9379-3CDEDA4B5968}.Release|x64.Deploy.0 = Release|x64 25 | {0A90B0A5-939F-4E28-9379-3CDEDA4B5968}.Release|x86.ActiveCfg = Release|Win32 26 | {0A90B0A5-939F-4E28-9379-3CDEDA4B5968}.Release|x86.Build.0 = Release|Win32 27 | {0A90B0A5-939F-4E28-9379-3CDEDA4B5968}.Release|x86.Deploy.0 = Release|Win32 28 | EndGlobalSection 29 | GlobalSection(SolutionProperties) = preSolution 30 | HideSolutionNode = FALSE 31 | EndGlobalSection 32 | GlobalSection(ExtensibilityGlobals) = postSolution 33 | SolutionGuid = {2C81CAFA-1FDC-4A37-9CE6-6FB0E5DEF49B} 34 | EndGlobalSection 35 | EndGlobal 36 | -------------------------------------------------------------------------------- /test/build_test.inf: -------------------------------------------------------------------------------- 1 | ; 2 | ; build_test.inf 3 | ; 4 | 5 | [Version] 6 | Signature="$WINDOWS NT$" 7 | Class=Sample ; TODO: edit Class 8 | ClassGuid={78A1C341-4539-11d3-B88D-00C04FAD5171} ; TODO: edit ClassGuid 9 | Provider=%ManufacturerName% 10 | CatalogFile=build_test.cat 11 | DriverVer= ; TODO: set DriverVer in stampinf property pages 12 | 13 | [DestinationDirs] 14 | DefaultDestDir = 12 15 | build_test_Device_CoInstaller_CopyFiles = 11 16 | 17 | ; ================= Class section ===================== 18 | 19 | [ClassInstall32] 20 | Addreg=SampleClassReg 21 | 22 | [SampleClassReg] 23 | HKR,,,0,%ClassName% 24 | HKR,,Icon,,-5 25 | 26 | [SourceDisksNames] 27 | 1 = %DiskName%,,,"" 28 | 29 | [SourceDisksFiles] 30 | build_test.sys = 1,, 31 | WdfCoInstaller$KMDFCOINSTALLERVERSION$.dll=1 ; make sure the number matches with SourceDisksNames 32 | 33 | ;***************************************** 34 | ; Install Section 35 | ;***************************************** 36 | 37 | [Manufacturer] 38 | %ManufacturerName%=Standard,NT$ARCH$ 39 | 40 | [Standard.NT$ARCH$] 41 | %build_test.DeviceDesc%=build_test_Device, Root\build_test ; TODO: edit hw-id 42 | 43 | [build_test_Device.NT] 44 | CopyFiles=Drivers_Dir 45 | 46 | [Drivers_Dir] 47 | build_test.sys 48 | 49 | ;-------------- Service installation 50 | [build_test_Device.NT.Services] 51 | AddService = build_test,%SPSVCINST_ASSOCSERVICE%, build_test_Service_Inst 52 | 53 | ; -------------- build_test driver install sections 54 | [build_test_Service_Inst] 55 | DisplayName = %build_test.SVCDESC% 56 | ServiceType = 1 ; SERVICE_KERNEL_DRIVER 57 | StartType = 3 ; SERVICE_DEMAND_START 58 | ErrorControl = 1 ; SERVICE_ERROR_NORMAL 59 | ServiceBinary = %12%\build_test.sys 60 | 61 | ; 62 | ;--- build_test_Device Coinstaller installation ------ 63 | ; 64 | 65 | [build_test_Device.NT.CoInstallers] 66 | AddReg=build_test_Device_CoInstaller_AddReg 67 | CopyFiles=build_test_Device_CoInstaller_CopyFiles 68 | 69 | [build_test_Device_CoInstaller_AddReg] 70 | HKR,,CoInstallers32,0x00010000, "WdfCoInstaller$KMDFCOINSTALLERVERSION$.dll,WdfCoInstaller" 71 | 72 | [build_test_Device_CoInstaller_CopyFiles] 73 | WdfCoInstaller$KMDFCOINSTALLERVERSION$.dll 74 | 75 | [build_test_Device.NT.Wdf] 76 | KmdfService = build_test, build_test_wdfsect 77 | [build_test_wdfsect] 78 | KmdfLibraryVersion = $KMDFVERSION$ 79 | 80 | [Strings] 81 | SPSVCINST_ASSOCSERVICE= 0x00000002 82 | ManufacturerName="" ;TODO: Replace with your manufacturer name 83 | ClassName="Samples" ; TODO: edit ClassName 84 | DiskName = "build_test Installation Disk" 85 | build_test.DeviceDesc = "build_test Device" 86 | build_test.SVCDESC = "build_test Service" 87 | -------------------------------------------------------------------------------- /test/build_test.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {0A90B0A5-939F-4E28-9379-3CDEDA4B5968} 23 | {1bc93793-694f-48fe-9372-81e2b05556fd} 24 | v4.5 25 | 12.0 26 | Debug 27 | Win32 28 | build_test 29 | 30 | 31 | 32 | Windows10 33 | true 34 | WindowsKernelModeDriver10.0 35 | Driver 36 | KMDF 37 | Universal 38 | 39 | 40 | Windows10 41 | false 42 | WindowsKernelModeDriver10.0 43 | Driver 44 | KMDF 45 | Universal 46 | 47 | 48 | Windows10 49 | true 50 | WindowsKernelModeDriver10.0 51 | Driver 52 | KMDF 53 | Universal 54 | 55 | 56 | Windows10 57 | false 58 | WindowsKernelModeDriver10.0 59 | Driver 60 | KMDF 61 | Universal 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | DbgengKernelDebugger 85 | 86 | 87 | DbgengKernelDebugger 88 | 89 | 90 | DbgengKernelDebugger 91 | 92 | 93 | DbgengKernelDebugger 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /test/build_test.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {8E41214B-6785-4CFE-B992-037D68949A14} 6 | inf;inv;inx;mof;mc; 7 | 8 | 9 | 10 | 11 | Driver Files 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | extern "C" DRIVER_INITIALIZE DriverEntry; 4 | 5 | extern "C" NTSTATUS DriverEntry( 6 | PDRIVER_OBJECT DriverObject, 7 | PUNICODE_STRING RegistryPath 8 | ) 9 | { 10 | try 11 | { 12 | throw 1; 13 | } 14 | catch (int) 15 | { 16 | } 17 | 18 | (void)DriverObject; 19 | (void)RegistryPath; 20 | return STATUS_SUCCESS; 21 | } 22 | --------------------------------------------------------------------------------