diff --git a/spec/std/exception/call_stack_spec.cr b/spec/std/exception/call_stack_spec.cr
index c01fb0ff6b8a..7c6f5d746bdc 100644
--- a/spec/std/exception/call_stack_spec.cr
+++ b/spec/std/exception/call_stack_spec.cr
@@ -12,9 +12,9 @@ describe "Backtrace" do
 
     _, output, _ = compile_and_run_file(source_file)
 
-    # resolved file:line:column (no column for windows PDB because of poor
-    # support in general)
-    {% if flag?(:win32) %}
+    # resolved file:line:column (no column for MSVC PDB because of poor support
+    # by external tooling in general)
+    {% if flag?(:msvc) %}
       output.should match(/^#{Regex.escape(source_file)}:3 in 'callee1'/m)
       output.should match(/^#{Regex.escape(source_file)}:13 in 'callee3'/m)
     {% else %}
diff --git a/src/crystal/pe.cr b/src/crystal/pe.cr
new file mode 100644
index 000000000000..d1b19401ad19
--- /dev/null
+++ b/src/crystal/pe.cr
@@ -0,0 +1,110 @@
+module Crystal
+  # :nodoc:
+  #
+  # Portable Executable reader.
+  #
+  # Documentation:
+  # - <https://learn.microsoft.com/en-us/windows/win32/debug/pe-format>
+  struct PE
+    class Error < Exception
+    end
+
+    record SectionHeader, name : String, virtual_offset : UInt32, offset : UInt32, size : UInt32
+
+    record COFFSymbol, offset : UInt32, name : String
+
+    # addresses in COFF debug info are relative to this image base; used by
+    # `Exception::CallStack.read_dwarf_sections` to calculate the real relocated
+    # addresses
+    getter original_image_base : UInt64
+
+    @section_headers : Slice(SectionHeader)
+    @string_table_base : UInt32
+
+    # mapping from zero-based section index to list of symbols sorted by
+    # offsets within that section
+    getter coff_symbols = Hash(Int32, Array(COFFSymbol)).new
+
+    def self.open(path : String | ::Path, &)
+      File.open(path, "r") do |file|
+        yield new(file)
+      end
+    end
+
+    def initialize(@io : IO::FileDescriptor)
+      dos_header = uninitialized LibC::IMAGE_DOS_HEADER
+      io.read_fully(pointerof(dos_header).to_slice(1).to_unsafe_bytes)
+      raise Error.new("Invalid DOS header") unless dos_header.e_magic == 0x5A4D # MZ
+
+      io.seek(dos_header.e_lfanew)
+      nt_header = uninitialized LibC::IMAGE_NT_HEADERS
+      io.read_fully(pointerof(nt_header).to_slice(1).to_unsafe_bytes)
+      raise Error.new("Invalid PE header") unless nt_header.signature == 0x00004550 # PE\0\0
+
+      @original_image_base = nt_header.optionalHeader.imageBase
+      @string_table_base = nt_header.fileHeader.pointerToSymbolTable + nt_header.fileHeader.numberOfSymbols * sizeof(LibC::IMAGE_SYMBOL)
+
+      section_count = nt_header.fileHeader.numberOfSections
+      nt_section_headers = Pointer(LibC::IMAGE_SECTION_HEADER).malloc(section_count).to_slice(section_count)
+      io.read_fully(nt_section_headers.to_unsafe_bytes)
+
+      @section_headers = nt_section_headers.map do |nt_header|
+        if nt_header.name[0] === '/'
+          # section name is longer than 8 bytes; look up the COFF string table
+          name_buf = nt_header.name.to_slice + 1
+          string_offset = String.new(name_buf.to_unsafe, name_buf.index(0) || name_buf.size).to_i
+          io.seek(@string_table_base + string_offset)
+          name = io.gets('\0', chomp: true).not_nil!
+        else
+          name = String.new(nt_header.name.to_unsafe, nt_header.name.index(0) || nt_header.name.size)
+        end
+
+        SectionHeader.new(name: name, virtual_offset: nt_header.virtualAddress, offset: nt_header.pointerToRawData, size: nt_header.virtualSize)
+      end
+
+      io.seek(nt_header.fileHeader.pointerToSymbolTable)
+      image_symbol_count = nt_header.fileHeader.numberOfSymbols
+      image_symbols = Pointer(LibC::IMAGE_SYMBOL).malloc(image_symbol_count).to_slice(image_symbol_count)
+      io.read_fully(image_symbols.to_unsafe_bytes)
+
+      aux_count = 0
+      image_symbols.each_with_index do |sym, i|
+        if aux_count == 0
+          aux_count = sym.numberOfAuxSymbols.to_i
+        else
+          aux_count &-= 1
+        end
+
+        next unless aux_count == 0
+        next unless sym.type.bits_set?(0x20) # COFF function
+        next unless sym.sectionNumber > 0    # one-based section index
+        next unless sym.storageClass.in?(LibC::IMAGE_SYM_CLASS_EXTERNAL, LibC::IMAGE_SYM_CLASS_STATIC)
+
+        if sym.n.name.short == 0
+          io.seek(@string_table_base + sym.n.name.long)
+          name = io.gets('\0', chomp: true).not_nil!
+        else
+          name = String.new(sym.n.shortName.to_slice).rstrip('\0')
+        end
+
+        # `@coff_symbols` uses zero-based indices
+        section_coff_symbols = @coff_symbols.put_if_absent(sym.sectionNumber.to_i &- 1) { [] of COFFSymbol }
+        section_coff_symbols << COFFSymbol.new(sym.value, name)
+      end
+
+      # add one sentinel symbol to ensure binary search on the offsets works
+      @coff_symbols.each_with_index do |(_, symbols), i|
+        symbols.sort_by!(&.offset)
+        symbols << COFFSymbol.new(@section_headers[i].size, "??")
+      end
+    end
+
+    def read_section?(name : String, &)
+      if sh = @section_headers.find(&.name.== name)
+        @io.seek(sh.offset) do
+          yield sh, @io
+        end
+      end
+    end
+  end
+end
diff --git a/src/crystal/system/win32/signal.cr b/src/crystal/system/win32/signal.cr
index d805ea4fd1ab..4cebe7cf9c6a 100644
--- a/src/crystal/system/win32/signal.cr
+++ b/src/crystal/system/win32/signal.cr
@@ -1,4 +1,5 @@
 require "c/signal"
+require "c/malloc"
 
 module Crystal::System::Signal
   def self.trap(signal, handler) : Nil
@@ -16,4 +17,47 @@ module Crystal::System::Signal
   def self.ignore(signal) : Nil
     raise NotImplementedError.new("Crystal::System::Signal.ignore")
   end
+
+  def self.setup_seh_handler
+    LibC.AddVectoredExceptionHandler(1, ->(exception_info) do
+      case exception_info.value.exceptionRecord.value.exceptionCode
+      when LibC::EXCEPTION_ACCESS_VIOLATION
+        addr = exception_info.value.exceptionRecord.value.exceptionInformation[1]
+        Crystal::System.print_error "Invalid memory access (C0000005) at address %p\n", Pointer(Void).new(addr)
+        {% if flag?(:gnu) %}
+          Exception::CallStack.print_backtrace
+        {% else %}
+          Exception::CallStack.print_backtrace(exception_info)
+        {% end %}
+        LibC._exit(1)
+      when LibC::EXCEPTION_STACK_OVERFLOW
+        LibC._resetstkoflw
+        Crystal::System.print_error "Stack overflow (e.g., infinite or very deep recursion)\n"
+        {% if flag?(:gnu) %}
+          Exception::CallStack.print_backtrace
+        {% else %}
+          Exception::CallStack.print_backtrace(exception_info)
+        {% end %}
+        LibC._exit(1)
+      else
+        LibC::EXCEPTION_CONTINUE_SEARCH
+      end
+    end)
+
+    # ensure that even in the case of stack overflow there is enough reserved
+    # stack space for recovery (for other threads this is done in
+    # `Crystal::System::Thread.thread_proc`)
+    stack_size = Crystal::System::Fiber::RESERVED_STACK_SIZE
+    LibC.SetThreadStackGuarantee(pointerof(stack_size))
+
+    # this catches invalid argument checks inside the C runtime library
+    LibC._set_invalid_parameter_handler(->(expression, _function, _file, _line, _pReserved) do
+      message = expression ? String.from_utf16(expression)[0] : "(no message)"
+      Crystal::System.print_error "CRT invalid parameter handler invoked: %s\n", message
+      caller.each do |frame|
+        Crystal::System.print_error "  from %s\n", frame
+      end
+      LibC._exit(1)
+    end)
+  end
 end
diff --git a/src/exception/call_stack.cr b/src/exception/call_stack.cr
index 44a281570c1c..506317d2580e 100644
--- a/src/exception/call_stack.cr
+++ b/src/exception/call_stack.cr
@@ -1,10 +1,7 @@
 {% if flag?(:interpreted) %}
   require "./call_stack/interpreter"
-{% elsif flag?(:win32) %}
+{% elsif flag?(:win32) && !flag?(:gnu) %}
   require "./call_stack/stackwalk"
-  {% if flag?(:gnu) %}
-    require "./lib_unwind"
-  {% end %}
 {% elsif flag?(:wasm32) %}
   require "./call_stack/null"
 {% else %}
diff --git a/src/exception/call_stack/dwarf.cr b/src/exception/call_stack/dwarf.cr
index 96d99f03205a..253a72a38ebc 100644
--- a/src/exception/call_stack/dwarf.cr
+++ b/src/exception/call_stack/dwarf.cr
@@ -10,6 +10,10 @@ struct Exception::CallStack
   @@dwarf_line_numbers : Crystal::DWARF::LineNumbers?
   @@dwarf_function_names : Array(Tuple(LibC::SizeT, LibC::SizeT, String))?
 
+  {% if flag?(:win32) %}
+    @@coff_symbols : Hash(Int32, Array(Crystal::PE::COFFSymbol))?
+  {% end %}
+
   # :nodoc:
   def self.load_debug_info : Nil
     return if ENV["CRYSTAL_LOAD_DEBUG_INFO"]? == "0"
diff --git a/src/exception/call_stack/elf.cr b/src/exception/call_stack/elf.cr
index efa54f41329c..51d565528577 100644
--- a/src/exception/call_stack/elf.cr
+++ b/src/exception/call_stack/elf.cr
@@ -1,65 +1,83 @@
-require "crystal/elf"
-{% unless flag?(:wasm32) %}
-  require "c/link"
+{% if flag?(:win32) %}
+  require "crystal/pe"
+{% else %}
+  require "crystal/elf"
+  {% unless flag?(:wasm32) %}
+    require "c/link"
+  {% end %}
 {% end %}
 
 struct Exception::CallStack
-  private struct DlPhdrData
-    getter program : String
-    property base_address : LibC::Elf_Addr = 0
+  {% unless flag?(:win32) %}
+    private struct DlPhdrData
+      getter program : String
+      property base_address : LibC::Elf_Addr = 0
 
-    def initialize(@program : String)
+      def initialize(@program : String)
+      end
     end
-  end
+  {% end %}
 
   protected def self.load_debug_info_impl : Nil
     program = Process.executable_path
     return unless program && File::Info.readable? program
-    data = DlPhdrData.new(program)
-
-    phdr_callback = LibC::DlPhdrCallback.new do |info, size, data|
-      # `dl_iterate_phdr` does not always visit the current program first; on
-      # Android the first object is `/system/bin/linker64`, the second is the
-      # full program path (not the empty string), so we check both here
-      name_c_str = info.value.name
-      if name_c_str && (name_c_str.value == 0 || LibC.strcmp(name_c_str, data.as(DlPhdrData*).value.program) == 0)
-        # The first entry is the header for the current program.
-        # Note that we avoid allocating here and just store the base address
-        # to be passed to self.read_dwarf_sections when dl_iterate_phdr returns.
-        # Calling self.read_dwarf_sections from this callback may lead to reallocations
-        # and deadlocks due to the internal lock held by dl_iterate_phdr (#10084).
-        data.as(DlPhdrData*).value.base_address = info.value.addr
-        1
-      else
-        0
+
+    {% if flag?(:win32) %}
+      if LibC.GetModuleHandleExW(LibC::GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, nil, out hmodule) != 0
+        self.read_dwarf_sections(program, hmodule.address)
       end
-    end
+    {% else %}
+      data = DlPhdrData.new(program)
 
-    LibC.dl_iterate_phdr(phdr_callback, pointerof(data))
-    self.read_dwarf_sections(data.program, data.base_address)
+      phdr_callback = LibC::DlPhdrCallback.new do |info, size, data|
+        # `dl_iterate_phdr` does not always visit the current program first; on
+        # Android the first object is `/system/bin/linker64`, the second is the
+        # full program path (not the empty string), so we check both here
+        name_c_str = info.value.name
+        if name_c_str && (name_c_str.value == 0 || LibC.strcmp(name_c_str, data.as(DlPhdrData*).value.program) == 0)
+          # The first entry is the header for the current program.
+          # Note that we avoid allocating here and just store the base address
+          # to be passed to self.read_dwarf_sections when dl_iterate_phdr returns.
+          # Calling self.read_dwarf_sections from this callback may lead to reallocations
+          # and deadlocks due to the internal lock held by dl_iterate_phdr (#10084).
+          data.as(DlPhdrData*).value.base_address = info.value.addr
+          1
+        else
+          0
+        end
+      end
+
+      LibC.dl_iterate_phdr(phdr_callback, pointerof(data))
+      self.read_dwarf_sections(data.program, data.base_address)
+    {% end %}
   end
 
   protected def self.read_dwarf_sections(program, base_address = 0)
-    Crystal::ELF.open(program) do |elf|
-      line_strings = elf.read_section?(".debug_line_str") do |sh, io|
+    {{ flag?(:win32) ? Crystal::PE : Crystal::ELF }}.open(program) do |image|
+      {% if flag?(:win32) %}
+        base_address -= image.original_image_base
+        @@coff_symbols = image.coff_symbols
+      {% end %}
+
+      line_strings = image.read_section?(".debug_line_str") do |sh, io|
         Crystal::DWARF::Strings.new(io, sh.offset, sh.size)
       end
 
-      strings = elf.read_section?(".debug_str") do |sh, io|
+      strings = image.read_section?(".debug_str") do |sh, io|
         Crystal::DWARF::Strings.new(io, sh.offset, sh.size)
       end
 
-      elf.read_section?(".debug_line") do |sh, io|
+      image.read_section?(".debug_line") do |sh, io|
         @@dwarf_line_numbers = Crystal::DWARF::LineNumbers.new(io, sh.size, base_address, strings, line_strings)
       end
 
-      elf.read_section?(".debug_info") do |sh, io|
+      image.read_section?(".debug_info") do |sh, io|
         names = [] of {LibC::SizeT, LibC::SizeT, String}
 
         while (offset = io.pos - sh.offset) < sh.size
           info = Crystal::DWARF::Info.new(io, offset)
 
-          elf.read_section?(".debug_abbrev") do |sh, io|
+          image.read_section?(".debug_abbrev") do |sh, io|
             info.read_abbreviations(io)
           end
 
diff --git a/src/exception/call_stack/libunwind.cr b/src/exception/call_stack/libunwind.cr
index 1542d52cc736..c0f75867aeba 100644
--- a/src/exception/call_stack/libunwind.cr
+++ b/src/exception/call_stack/libunwind.cr
@@ -1,9 +1,11 @@
-require "c/dlfcn"
+{% unless flag?(:win32) %}
+  require "c/dlfcn"
+{% end %}
 require "c/stdio"
 require "c/string"
 require "../lib_unwind"
 
-{% if flag?(:darwin) || flag?(:bsd) || flag?(:linux) || flag?(:solaris) %}
+{% if flag?(:darwin) || flag?(:bsd) || flag?(:linux) || flag?(:solaris) || flag?(:win32) %}
   require "./dwarf"
 {% else %}
   require "./null"
@@ -33,7 +35,11 @@ struct Exception::CallStack
   {% end %}
 
   def self.setup_crash_handler
-    Crystal::System::Signal.setup_segfault_handler
+    {% if flag?(:win32) %}
+      Crystal::System::Signal.setup_seh_handler
+    {% else %}
+      Crystal::System::Signal.setup_segfault_handler
+    {% end %}
   end
 
   {% if flag?(:interpreted) %} @[Primitive(:interpreter_call_stack_unwind)] {% end %}
@@ -167,9 +173,102 @@ struct Exception::CallStack
     end
   end
 
-  private def self.dladdr(ip, &)
-    if LibC.dladdr(ip, out info) != 0
-      yield info.dli_fname, info.dli_sname, info.dli_saddr
+  {% if flag?(:win32) %}
+    def self.dladdr(ip, &)
+      if LibC.GetModuleHandleExW(LibC::GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT | LibC::GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, ip.as(LibC::LPWSTR), out hmodule) != 0
+        symbol, address = internal_symbol(hmodule, ip) || external_symbol(hmodule, ip) || return
+
+        utf16_file = uninitialized LibC::WCHAR[LibC::MAX_PATH]
+        len = LibC.GetModuleFileNameW(hmodule, utf16_file, utf16_file.size)
+        if 0 < len < utf16_file.size
+          utf8_file = uninitialized UInt8[sizeof(UInt8[LibC::MAX_PATH][3])]
+          file = utf8_file.to_unsafe
+          appender = file.appender
+          String.each_utf16_char(utf16_file.to_slice[0, len + 1]) do |ch|
+            ch.each_byte { |b| appender << b }
+          end
+        else
+          file = Pointer(UInt8).null
+        end
+
+        yield file, symbol, address
+      end
     end
-  end
+
+    private def self.internal_symbol(hmodule, ip)
+      if coff_symbols = @@coff_symbols
+        if LibC.GetModuleHandleExW(LibC::GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, nil, out this_hmodule) != 0 && this_hmodule == hmodule
+          section_base, section_index = lookup_section(hmodule, ip) || return
+          offset = ip - section_base
+          section_coff_symbols = coff_symbols[section_index]? || return
+          next_sym = section_coff_symbols.bsearch_index { |sym| offset < sym.offset } || return
+          sym = section_coff_symbols[next_sym - 1]? || return
+
+          {sym.name.to_unsafe, section_base + sym.offset}
+        end
+      end
+    end
+
+    private def self.external_symbol(hmodule, ip)
+      if dir = data_directory(hmodule, LibC::IMAGE_DIRECTORY_ENTRY_EXPORT)
+        exports = dir.to_unsafe.as(LibC::IMAGE_EXPORT_DIRECTORY*).value
+
+        found_address = Pointer(Void).null
+        found_index = -1
+
+        func_address_offsets = (hmodule + exports.addressOfFunctions).as(LibC::DWORD*).to_slice(exports.numberOfFunctions)
+        func_address_offsets.each_with_index do |offset, i|
+          address = hmodule + offset
+          if found_address < address <= ip
+            found_address, found_index = address, i
+          end
+        end
+
+        return unless found_address
+
+        func_name_ordinals = (hmodule + exports.addressOfNameOrdinals).as(LibC::WORD*).to_slice(exports.numberOfNames)
+        if ordinal_index = func_name_ordinals.index(&.== found_index)
+          symbol = (hmodule + (hmodule + exports.addressOfNames).as(LibC::DWORD*)[ordinal_index]).as(UInt8*)
+          {symbol, found_address}
+        end
+      end
+    end
+
+    private def self.lookup_section(hmodule, ip)
+      dos_header = hmodule.as(LibC::IMAGE_DOS_HEADER*)
+      return unless dos_header.value.e_magic == 0x5A4D # MZ
+
+      nt_header = (hmodule + dos_header.value.e_lfanew).as(LibC::IMAGE_NT_HEADERS*)
+      return unless nt_header.value.signature == 0x00004550 # PE\0\0
+
+      section_headers = (nt_header + 1).as(LibC::IMAGE_SECTION_HEADER*).to_slice(nt_header.value.fileHeader.numberOfSections)
+      section_headers.each_with_index do |header, i|
+        base = hmodule + header.virtualAddress
+        if base <= ip < base + header.virtualSize
+          return base, i
+        end
+      end
+    end
+
+    private def self.data_directory(hmodule, index)
+      dos_header = hmodule.as(LibC::IMAGE_DOS_HEADER*)
+      return unless dos_header.value.e_magic == 0x5A4D # MZ
+
+      nt_header = (hmodule + dos_header.value.e_lfanew).as(LibC::IMAGE_NT_HEADERS*)
+      return unless nt_header.value.signature == 0x00004550 # PE\0\0
+      return unless nt_header.value.optionalHeader.magic == {{ flag?(:bits64) ? 0x20b : 0x10b }}
+      return unless index.in?(0...{16, nt_header.value.optionalHeader.numberOfRvaAndSizes}.min)
+
+      directory = nt_header.value.optionalHeader.dataDirectory.to_unsafe[index]
+      if directory.virtualAddress != 0
+        Bytes.new(hmodule.as(UInt8*) + directory.virtualAddress, directory.size, read_only: true)
+      end
+    end
+  {% else %}
+    private def self.dladdr(ip, &)
+      if LibC.dladdr(ip, out info) != 0
+        yield info.dli_fname, info.dli_sname, info.dli_saddr
+      end
+    end
+  {% end %}
 end
diff --git a/src/exception/call_stack/stackwalk.cr b/src/exception/call_stack/stackwalk.cr
index 6ac59fa6db48..d7e3da8e35f1 100644
--- a/src/exception/call_stack/stackwalk.cr
+++ b/src/exception/call_stack/stackwalk.cr
@@ -1,5 +1,4 @@
 require "c/dbghelp"
-require "c/malloc"
 
 # :nodoc:
 struct Exception::CallStack
@@ -33,38 +32,7 @@ struct Exception::CallStack
   end
 
   def self.setup_crash_handler
-    LibC.AddVectoredExceptionHandler(1, ->(exception_info) do
-      case exception_info.value.exceptionRecord.value.exceptionCode
-      when LibC::EXCEPTION_ACCESS_VIOLATION
-        addr = exception_info.value.exceptionRecord.value.exceptionInformation[1]
-        Crystal::System.print_error "Invalid memory access (C0000005) at address %p\n", Pointer(Void).new(addr)
-        print_backtrace(exception_info)
-        LibC._exit(1)
-      when LibC::EXCEPTION_STACK_OVERFLOW
-        LibC._resetstkoflw
-        Crystal::System.print_error "Stack overflow (e.g., infinite or very deep recursion)\n"
-        print_backtrace(exception_info)
-        LibC._exit(1)
-      else
-        LibC::EXCEPTION_CONTINUE_SEARCH
-      end
-    end)
-
-    # ensure that even in the case of stack overflow there is enough reserved
-    # stack space for recovery (for other threads this is done in
-    # `Crystal::System::Thread.thread_proc`)
-    stack_size = Crystal::System::Fiber::RESERVED_STACK_SIZE
-    LibC.SetThreadStackGuarantee(pointerof(stack_size))
-
-    # this catches invalid argument checks inside the C runtime library
-    LibC._set_invalid_parameter_handler(->(expression, _function, _file, _line, _pReserved) do
-      message = expression ? String.from_utf16(expression)[0] : "(no message)"
-      Crystal::System.print_error "CRT invalid parameter handler invoked: %s\n", message
-      caller.each do |frame|
-        Crystal::System.print_error "  from %s\n", frame
-      end
-      LibC._exit(1)
-    end)
+    Crystal::System::Signal.setup_seh_handler
   end
 
   {% if flag?(:interpreted) %} @[Primitive(:interpreter_call_stack_unwind)] {% end %}
@@ -168,33 +136,6 @@ struct Exception::CallStack
     end
   end
 
-  # TODO: needed only if `__crystal_raise` fails, check if this actually works
-  {% if flag?(:gnu) %}
-    def self.print_backtrace : Nil
-      backtrace_fn = ->(context : LibUnwind::Context, data : Void*) do
-        last_frame = data.as(RepeatedFrame*)
-
-        ip = {% if flag?(:arm) %}
-              Pointer(Void).new(__crystal_unwind_get_ip(context))
-            {% else %}
-              Pointer(Void).new(LibUnwind.get_ip(context))
-            {% end %}
-
-        if last_frame.value.ip == ip
-          last_frame.value.incr
-        else
-          print_frame(last_frame.value) unless last_frame.value.ip.address == 0
-          last_frame.value = RepeatedFrame.new ip
-        end
-        LibUnwind::ReasonCode::NO_REASON
-      end
-
-      rf = RepeatedFrame.new(Pointer(Void).null)
-      LibUnwind.backtrace(backtrace_fn, pointerof(rf).as(Void*))
-      print_frame(rf)
-    end
-  {% end %}
-
   private def self.print_frame(repeated_frame)
     Crystal::System.print_error "[%p] ", repeated_frame.ip
     print_frame_location(repeated_frame)
diff --git a/src/lib_c/x86_64-windows-msvc/c/libloaderapi.cr b/src/lib_c/x86_64-windows-msvc/c/libloaderapi.cr
index 37a95f3fa089..5612233553d9 100644
--- a/src/lib_c/x86_64-windows-msvc/c/libloaderapi.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/libloaderapi.cr
@@ -9,6 +9,9 @@ lib LibC
   fun LoadLibraryExW(lpLibFileName : LPWSTR, hFile : HANDLE, dwFlags : DWORD) : HMODULE
   fun FreeLibrary(hLibModule : HMODULE) : BOOL
 
+  GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT = 0x00000002
+  GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS       = 0x00000004
+
   fun GetModuleHandleExW(dwFlags : DWORD, lpModuleName : LPWSTR, phModule : HMODULE*) : BOOL
 
   fun GetProcAddress(hModule : HMODULE, lpProcName : LPSTR) : FARPROC
diff --git a/src/lib_c/x86_64-windows-msvc/c/winnt.cr b/src/lib_c/x86_64-windows-msvc/c/winnt.cr
index 1db4b2def700..e9aecc01e033 100644
--- a/src/lib_c/x86_64-windows-msvc/c/winnt.cr
+++ b/src/lib_c/x86_64-windows-msvc/c/winnt.cr
@@ -392,11 +392,65 @@ lib LibC
     optionalHeader : IMAGE_OPTIONAL_HEADER64
   end
 
+  IMAGE_DIRECTORY_ENTRY_EXPORT =  0
+  IMAGE_DIRECTORY_ENTRY_IMPORT =  1
+  IMAGE_DIRECTORY_ENTRY_IAT    = 12
+
+  struct IMAGE_SECTION_HEADER
+    name : BYTE[8]
+    virtualSize : DWORD
+    virtualAddress : DWORD
+    sizeOfRawData : DWORD
+    pointerToRawData : DWORD
+    pointerToRelocations : DWORD
+    pointerToLinenumbers : DWORD
+    numberOfRelocations : WORD
+    numberOfLinenumbers : WORD
+    characteristics : DWORD
+  end
+
+  struct IMAGE_EXPORT_DIRECTORY
+    characteristics : DWORD
+    timeDateStamp : DWORD
+    majorVersion : WORD
+    minorVersion : WORD
+    name : DWORD
+    base : DWORD
+    numberOfFunctions : DWORD
+    numberOfNames : DWORD
+    addressOfFunctions : DWORD
+    addressOfNames : DWORD
+    addressOfNameOrdinals : DWORD
+  end
+
   struct IMAGE_IMPORT_BY_NAME
     hint : WORD
     name : CHAR[1]
   end
 
+  struct IMAGE_SYMBOL_n_name
+    short : DWORD
+    long : DWORD
+  end
+
+  union IMAGE_SYMBOL_n
+    shortName : BYTE[8]
+    name : IMAGE_SYMBOL_n_name
+  end
+
+  IMAGE_SYM_CLASS_EXTERNAL = 2
+  IMAGE_SYM_CLASS_STATIC   = 3
+
+  @[Packed]
+  struct IMAGE_SYMBOL
+    n : IMAGE_SYMBOL_n
+    value : DWORD
+    sectionNumber : Short
+    type : WORD
+    storageClass : BYTE
+    numberOfAuxSymbols : BYTE
+  end
+
   union IMAGE_THUNK_DATA64_u1
     forwarderString : ULongLong
     function : ULongLong
diff --git a/src/raise.cr b/src/raise.cr
index a8e06a3c3930..0c9563495a94 100644
--- a/src/raise.cr
+++ b/src/raise.cr
@@ -181,7 +181,7 @@ end
     0u64
   end
 {% else %}
-  {% mingw = flag?(:windows) && flag?(:gnu) %}
+  {% mingw = flag?(:win32) && flag?(:gnu) %}
   fun {{ mingw ? "__crystal_personality_imp".id : "__crystal_personality".id }}(
     version : Int32, actions : LibUnwind::Action, exception_class : UInt64, exception_object : LibUnwind::Exception*, context : Void*,
   ) : LibUnwind::ReasonCode