diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 69dfb1545d46..11aec109258e 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -172,7 +172,7 @@ jobs: cl /MT /c src\llvm\ext\llvm_ext.cc -I llvm\include /Fosrc\llvm\ext\llvm_ext.obj - name: Link Crystal executable run: | - Invoke-Expression "cl crystal.obj /Fecrystal-cross src\llvm\ext\llvm_ext.obj $(llvm\bin\llvm-config.exe --libs) libs\pcre.lib libs\gc.lib WS2_32.lib advapi32.lib libcmt.lib legacy_stdio_definitions.lib /F10000000" + Invoke-Expression "cl crystal.obj /Fecrystal-cross src\llvm\ext\llvm_ext.obj $(llvm\bin\llvm-config.exe --libs) libs\pcre.lib libs\gc.lib WS2_32.lib advapi32.lib libcmt.lib dbghelp.lib legacy_stdio_definitions.lib /F10000000" - name: Re-build Crystal run: | diff --git a/spec/std/exception/call_stack_spec.cr b/spec/std/exception/call_stack_spec.cr index bb9318d442ae..b0a33e5a467c 100644 --- a/spec/std/exception/call_stack_spec.cr +++ b/spec/std/exception/call_stack_spec.cr @@ -1,7 +1,7 @@ require "../spec_helper" describe "Backtrace" do - pending_win32 "prints file line:column" do + it "prints file line:column" do source_file = datapath("backtrace_sample") # CallStack tries to make files relative to the current dir, @@ -12,9 +12,15 @@ describe "Backtrace" do _, output, _ = compile_and_run_file(source_file) - # resolved file line:column - output.should match(/^#{source_file}:3:10 in 'callee1'/m) - output.should match(/^#{source_file}:13:5 in 'callee3'/m) + # resolved file:line:column (no column for windows PDB because of poor + # support in general) + {% if flag?(:win32) %} + output.should match(/^#{Regex.escape(source_file)}:3 in 'callee1'/m) + output.should match(/^#{Regex.escape(source_file)}:13 in 'callee3'/m) + {% else %} + output.should match(/^#{Regex.escape(source_file)}:3:10 in 'callee1'/m) + output.should match(/^#{Regex.escape(source_file)}:13:5 in 'callee3'/m) + {% end %} # skipped internal details output.should_not contain("src/callstack.cr") @@ -22,7 +28,7 @@ describe "Backtrace" do output.should_not contain("src/raise.cr") end - pending_win32 "doesn't relativize paths outside of current dir (#10169)" do + it "doesn't relativize paths outside of current dir (#10169)" do with_tempfile("source_file") do |source_file| source_path = Path.new(source_file) source_path.absolute?.should be_true @@ -36,7 +42,7 @@ describe "Backtrace" do EOF _, output, _ = compile_and_run_file(source_file) - output.should match /\A(#{source_path}):/ + output.should match /\A(#{Regex.escape(source_path.to_s)}):/ end end diff --git a/spec/std/raise_spec.cr b/spec/std/raise_spec.cr index 0d80aca5a1df..026741802df9 100644 --- a/spec/std/raise_spec.cr +++ b/spec/std/raise_spec.cr @@ -3,14 +3,14 @@ require "./spec_helper" describe "raise" do callstack_on_rescue = nil - pending_win32 "should set exception's callstack" do + it "should set exception's callstack" do exception = expect_raises Exception, "without callstack" do raise "without callstack" end exception.callstack.should_not be_nil end - pending_win32 "shouldn't overwrite the callstack on re-raise" do + it "shouldn't overwrite the callstack on re-raise" do exception_after_reraise = expect_raises Exception, "exception to be rescued" do begin raise "exception to be rescued" diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index 1efa7d170f5e..c4b0d6959bf5 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -28,6 +28,8 @@ module Crystal def evaluate(node, debug = Debug::Default) llvm_mod = codegen(node, single_module: true, debug: debug)[""].mod + llvm_mod.target = target_machine.triple + main = llvm_mod.functions[MAIN_NAME] main_return_type = main.return_type diff --git a/src/compiler/crystal/codegen/debug.cr b/src/compiler/crystal/codegen/debug.cr index feb58a32bd13..478017f25b2d 100644 --- a/src/compiler/crystal/codegen/debug.cr +++ b/src/compiler/crystal/codegen/debug.cr @@ -33,9 +33,16 @@ module Crystal def push_debug_info_metadata(mod) di_builder(mod).end - # DebugInfo generation in LLVM by default uses a higher version of dwarf - # than OS X currently understands. Android has the same problem. - if @program.has_flag?("osx") || @program.has_flag?("android") + if @program.has_flag?("windows") + # Windows uses CodeView instead of DWARF + mod.add_flag( + LLVM::ModuleFlag::Warning, + "CodeView", + mod.context.int32.const_int(1) + ) + elsif @program.has_flag?("osx") || @program.has_flag?("android") + # DebugInfo generation in LLVM by default uses a higher version of dwarf + # than OS X currently understands. Android has the same problem. mod.add_flag( LLVM::ModuleFlag::Warning, "Dwarf Version", diff --git a/src/compiler/crystal/compiler.cr b/src/compiler/crystal/compiler.cr index c657811dd067..3cd21fae2a84 100644 --- a/src/compiler/crystal/compiler.cr +++ b/src/compiler/crystal/compiler.cr @@ -346,7 +346,7 @@ module Crystal object_arg = Process.quote_windows(object_names) output_arg = Process.quote_windows("/Fe#{output_filename}") - args = %(/nologo #{object_arg} #{output_arg} /link #{lib_flags} #{@link_flags}).gsub("\n", " ") + args = %(/nologo #{object_arg} #{output_arg} /link#{" /DEBUG:FULL" unless debug.none?} #{lib_flags} #{@link_flags}).gsub("\n", " ") cmd = "#{CL} #{args}" if cmd.to_utf16.size > 32000 diff --git a/src/exception.cr b/src/exception.cr index 9987f886a3f2..6938bb92888f 100644 --- a/src/exception.cr +++ b/src/exception.cr @@ -35,11 +35,7 @@ class Exception # The backtrace is an array of strings, each containing # “0xAddress: Function at File Line Column”. def backtrace? - {% if flag?(:win32) %} - Array(String).new - {% else %} - @callstack.try &.printable_backtrace - {% end %} + @callstack.try &.printable_backtrace end def to_s(io : IO) : Nil diff --git a/src/exception/call_stack.cr b/src/exception/call_stack.cr index 498791d97a2b..035151ef8f07 100644 --- a/src/exception/call_stack.cr +++ b/src/exception/call_stack.cr @@ -1,6 +1,8 @@ -{% skip_file if flag?(:win32) %} - -require "./call_stack/libunwind" +{% if flag?(:win32) %} + require "./call_stack/stackwalk" +{% else %} + require "./call_stack/libunwind" +{% end %} # Returns the current execution stack as an array containing strings # usually in the form file:line:column or file:line:column in 'method'. @@ -12,12 +14,7 @@ end struct Exception::CallStack # Compute current directory at the beginning so filenames # are always shown relative to the *starting* working directory. - CURRENT_DIR = begin - if dir = Process::INITIAL_PWD - dir += File::SEPARATOR unless dir.ends_with?(File::SEPARATOR) - dir - end - end + private CURRENT_DIR = Process::INITIAL_PWD.try { |dir| Path[dir] } @@skip = [] of String @@ -37,4 +34,64 @@ struct Exception::CallStack def printable_backtrace : Array(String) @backtrace ||= decode_backtrace end + + private def decode_backtrace + show_full_info = ENV["CRYSTAL_CALLSTACK_FULL_INFO"]? == "1" + + @callstack.compact_map do |ip| + pc = CallStack.decode_address(ip) + + file, line_number, column_number = CallStack.decode_line_number(pc) + + if file && file != "??" + next if @@skip.includes?(file) + + # Turn to relative to the current dir, if possible + if current_dir = CURRENT_DIR + if rel = Path[file].relative_to?(current_dir) + rel = rel.to_s + file = rel unless rel.starts_with?("..") + end + end + + file_line_column = file + unless line_number == 0 + file_line_column = "#{file_line_column}:#{line_number}" + file_line_column = "#{file_line_column}:#{column_number}" unless column_number == 0 + end + end + + if name = CallStack.decode_function_name(pc) + function = name + elsif frame = CallStack.decode_frame(ip) + _, function, file = frame + # Crystal methods (their mangled name) start with `*`, so + # we remove that to have less clutter in the output. + function = function.lchop('*') + else + function = "??" + end + + if file_line_column + if show_full_info && (frame = CallStack.decode_frame(ip)) + _, sname, _ = frame + line = "#{file_line_column} in '#{sname}'" + else + line = "#{file_line_column} in '#{function}'" + end + else + if file == "??" && function == "??" + line = "???" + else + line = "#{file} in '#{function}'" + end + end + + if show_full_info + line = "#{line} at 0x#{ip.address.to_s(16)}" + end + + line + end + end end diff --git a/src/exception/call_stack/dwarf.cr b/src/exception/call_stack/dwarf.cr index bb5f124c0256..bf5a91c687e8 100644 --- a/src/exception/call_stack/dwarf.cr +++ b/src/exception/call_stack/dwarf.cr @@ -11,12 +11,13 @@ struct Exception::CallStack @@dwarf_function_names : Array(Tuple(LibC::SizeT, LibC::SizeT, String))? # :nodoc: - def self.load_dwarf + def self.load_debug_info + return if ENV["CRYSTAL_LOAD_DEBUG_INFO"]? == "0" + unless @@dwarf_loaded @@dwarf_loaded = true begin - return if ENV["CRYSTAL_LOAD_DWARF"]? == "0" - load_dwarf_impl + load_debug_info_impl rescue ex @@dwarf_line_numbers = nil @@dwarf_function_names = nil @@ -26,7 +27,7 @@ struct Exception::CallStack end protected def self.decode_line_number(pc) - load_dwarf + load_debug_info if ln = @@dwarf_line_numbers if row = ln.find(pc) return {row.path, row.line, row.column} @@ -36,7 +37,7 @@ struct Exception::CallStack end protected def self.decode_function_name(pc) - load_dwarf + load_debug_info if fn = @@dwarf_function_names fn.each do |(low_pc, high_pc, function_name)| return function_name if low_pc <= pc <= high_pc diff --git a/src/exception/call_stack/elf.cr b/src/exception/call_stack/elf.cr index 755a6a4ba93a..8c7bb45b3219 100644 --- a/src/exception/call_stack/elf.cr +++ b/src/exception/call_stack/elf.cr @@ -2,7 +2,7 @@ require "crystal/elf" require "c/link" struct Exception::CallStack - protected def self.load_dwarf_impl + protected def self.load_debug_info_impl phdr_callback = LibC::DlPhdrCallback.new do |info, size, data| # The first entry is the header for the current program read_dwarf_sections(info.value.addr) diff --git a/src/exception/call_stack/libunwind.cr b/src/exception/call_stack/libunwind.cr index 96369dfbe665..b05ddafbcf6b 100644 --- a/src/exception/call_stack/libunwind.cr +++ b/src/exception/call_stack/libunwind.cr @@ -129,59 +129,6 @@ struct Exception::CallStack end end - private def decode_backtrace - show_full_info = ENV["CRYSTAL_CALLSTACK_FULL_INFO"]? == "1" - - @callstack.compact_map do |ip| - pc = CallStack.decode_address(ip) - - file, line, column = CallStack.decode_line_number(pc) - - if file && file != "??" - next if @@skip.includes?(file) - - # Turn to relative to the current dir, if possible - if current_dir = CURRENT_DIR - file = file.lchop(current_dir) - end - - file_line_column = "#{file}:#{line}:#{column}" - end - - if name = CallStack.decode_function_name(pc) - function = name - elsif frame = CallStack.decode_frame(ip) - _, function, file = frame - # Crystal methods (their mangled name) start with `*`, so - # we remove that to have less clutter in the output. - function = function.lchop('*') - else - function = "??" - end - - if file_line_column - if show_full_info && (frame = CallStack.decode_frame(ip)) - _, sname, _ = frame - line = "#{file_line_column} in '#{sname}'" - else - line = "#{file_line_column} in '#{function}'" - end - else - if file == "??" && function == "??" - line = "???" - else - line = "#{file} in '#{function}'" - end - end - - if show_full_info - line = "#{line} at 0x#{ip.address.to_s(16)}" - end - - line - end - end - protected def self.decode_frame(ip, original_ip = ip) if LibC.dladdr(ip, out info) != 0 offset = original_ip - info.dli_saddr diff --git a/src/exception/call_stack/mach_o.cr b/src/exception/call_stack/mach_o.cr index a4cd742d74af..7f6bbc0d3756 100644 --- a/src/exception/call_stack/mach_o.cr +++ b/src/exception/call_stack/mach_o.cr @@ -9,7 +9,7 @@ end struct Exception::CallStack @@image_slide : LibC::Long? - protected def self.load_dwarf_impl + protected def self.load_debug_info_impl read_dwarf_sections end diff --git a/src/exception/call_stack/stackwalk.cr b/src/exception/call_stack/stackwalk.cr new file mode 100644 index 000000000000..fb3ae4744233 --- /dev/null +++ b/src/exception/call_stack/stackwalk.cr @@ -0,0 +1,128 @@ +require "c/dbghelp" + +# :nodoc: +struct Exception::CallStack + skip(__FILE__) + + @@sym_loaded = false + + def self.load_debug_info + return if ENV["CRYSTAL_LOAD_DEBUG_INFO"]? == "0" + + unless @@sym_loaded + @@sym_loaded = true + begin + load_debug_info_impl + rescue ex + Crystal::System.print_exception "Unable to load debug information", ex + end + end + end + + private def self.load_debug_info_impl + # TODO: figure out if and when to call SymCleanup (it cannot be done in + # `at_exit` because unhandled exceptions in `main_user_code` are printed + # after those handlers) + if LibC.SymInitializeW(LibC.GetCurrentProcess, nil, 1) == 0 + raise RuntimeError.from_winerror("SymInitializeW") + end + LibC.SymSetOptions(LibC.SymGetOptions | LibC::SYMOPT_UNDNAME | LibC::SYMOPT_LOAD_LINES | LibC::SYMOPT_FAIL_CRITICAL_ERRORS | LibC::SYMOPT_NO_PROMPTS) + end + + def self.unwind + load_debug_info + + machine_type = {% if flag?(:x86_64) %} + LibC::IMAGE_FILE_MACHINE_AMD64 + {% elsif flag?(:i386) %} + # TODO: use WOW64_CONTEXT in place of CONTEXT + {% raise "x86 not supported" %} + {% else %} + {% raise "architecture not supported" %} + {% end %} + + # TODO: use stack if possible (must be 16-byte aligned) + context = Pointer(LibC::CONTEXT).malloc(1) + context.value.contextFlags = LibC::CONTEXT_FULL + LibC.RtlCaptureContext(context) + + stack_frame = LibC::STACKFRAME64.new + stack_frame.addrPC.mode = LibC::ADDRESS_MODE::AddrModeFlat + stack_frame.addrFrame.mode = LibC::ADDRESS_MODE::AddrModeFlat + stack_frame.addrStack.mode = LibC::ADDRESS_MODE::AddrModeFlat + + stack_frame.addrPC.offset = context.value.rip + stack_frame.addrFrame.offset = context.value.rbp + stack_frame.addrStack.offset = context.value.rsp + + stack = [] of Void* + + while true + ret = LibC.StackWalk64( + machine_type, + LibC.GetCurrentProcess, + LibC.GetCurrentThread, + pointerof(stack_frame), + context, + nil, + nil, # ->LibC.SymFunctionTableAccess64, + nil, # ->LibC.SymGetModuleBase64, + nil + ) + break if ret == 0 + stack << Pointer(Void).new(stack_frame.addrPC.offset) + end + + stack + end + + protected def self.decode_line_number(pc) + load_debug_info + + line_info = uninitialized LibC::IMAGEHLP_LINEW64 + line_info.sizeOfStruct = sizeof(LibC::IMAGEHLP_LINEW64) + + if LibC.SymGetLineFromAddrW64(LibC.GetCurrentProcess, pc, out displacement, pointerof(line_info)) != 0 + file_name = String.from_utf16(line_info.fileName)[0] + line_number = line_info.lineNumber + else + line_number = 0 + end + + unless file_name + module_info = Pointer(LibC::IMAGEHLP_MODULEW64).malloc(1) + module_info.value.sizeOfStruct = sizeof(LibC::IMAGEHLP_MODULEW64) + + if LibC.SymGetModuleInfoW64(LibC.GetCurrentProcess, pc, module_info) != 0 + mod_displacement = pc - LibC.SymGetModuleBase64(LibC.GetCurrentProcess, pc) + image_name = String.from_utf16(module_info.value.loadedImageName.to_unsafe)[0] + file_name = "#{image_name} +#{mod_displacement}" + else + file_name = "??" + end + end + + {file_name, line_number, 0} + end + + protected def self.decode_function_name(pc) + load_debug_info + + symbol_size = sizeof(LibC::SYMBOL_INFOW) + (LibC::MAX_SYM_NAME - 1) * sizeof(LibC::WCHAR) + symbol = Pointer(UInt8).malloc(symbol_size).as(LibC::SYMBOL_INFOW*) + symbol.value.sizeOfStruct = sizeof(LibC::SYMBOL_INFOW) + symbol.value.maxNameLen = LibC::MAX_SYM_NAME + + sym_displacement = LibC::DWORD64.zero + if LibC.SymFromAddrW(LibC.GetCurrentProcess, pc, pointerof(sym_displacement), symbol) != 0 + String.from_utf16(symbol.value.name.to_unsafe.to_slice(symbol.value.nameLen)) + end + end + + protected def self.decode_frame(pc) + end + + protected def self.decode_address(ip) + ip.address + end +end diff --git a/src/kernel.cr b/src/kernel.cr index 27df6cbe5798..c698fd6d4dff 100644 --- a/src/kernel.cr +++ b/src/kernel.cr @@ -536,15 +536,13 @@ end Signal.setup_segfault_handler {% end %} -{% if !flag?(:win32) %} - # load dwarf on start up of the program is executed with CRYSTAL_LOAD_DWARF=1 - # this will make dwarf available on print_frame that is used by Crystal's segfault handler - # - # - CRYSTAL_LOAD_DWARF=0 will never use dwarf information (See Exception::CallStack.load_dwarf) - # - CRYSTAL_LOAD_DWARF=1 will load dwarf on startup - # - Other values will load dwarf on demand: when the backtrace of the first exception is generated - Exception::CallStack.load_dwarf if ENV["CRYSTAL_LOAD_DWARF"]? == "1" -{% end %} +# load debug info on start up of the program is executed with CRYSTAL_LOAD_DEBUG_INFO=1 +# this will make debug info available on print_frame that is used by Crystal's segfault handler +# +# - CRYSTAL_LOAD_DEBUG_INFO=0 will never use debug info (See Exception::CallStack.load_debug_info) +# - CRYSTAL_LOAD_DEBUG_INFO=1 will load debug info on startup +# - Other values will load debug info on demand: when the backtrace of the first exception is generated +Exception::CallStack.load_debug_info if ENV["CRYSTAL_LOAD_DEBUG_INFO"]? == "1" {% if flag?(:preview_mt) %} Crystal::Scheduler.init_workers diff --git a/src/lib_c/x86_64-windows-msvc/c/dbghelp.cr b/src/lib_c/x86_64-windows-msvc/c/dbghelp.cr new file mode 100644 index 000000000000..af37cb0c7f0c --- /dev/null +++ b/src/lib_c/x86_64-windows-msvc/c/dbghelp.cr @@ -0,0 +1,136 @@ +@[Link("DbgHelp")] +lib LibC + MAX_SYM_NAME = 2000 + + SYMOPT_UNDNAME = 0x00000002 + SYMOPT_LOAD_LINES = 0x00000010 + SYMOPT_FAIL_CRITICAL_ERRORS = 0x00000200 + SYMOPT_NO_PROMPTS = 0x00080000 + + struct SYMBOL_INFOW + sizeOfStruct : DWORD + typeIndex : DWORD + reserved : DWORD64[2] + index : DWORD + size : DWORD + modBase : DWORD64 + flags : DWORD + value : DWORD64 + address : DWORD64 + register : DWORD + scope : DWORD + tag : DWORD + nameLen : DWORD + maxNameLen : DWORD + name : WCHAR[1] # VLA + end + + struct IMAGEHLP_LINEW64 + sizeOfStruct : DWORD + key : Void* + lineNumber : DWORD + fileName : LPWSTR + address : DWORD64 + end + + alias SYM_TYPE = DWORD + + struct IMAGEHLP_MODULEW64 + sizeOfStruct : DWORD + baseOfImage : DWORD64 + imageSize : DWORD + timeDateStamp : DWORD + checkSum : DWORD + numSyms : DWORD + symType : SYM_TYPE + moduleName : WCHAR[32] + imageName : WCHAR[256] + loadedImageName : WCHAR[256] + loadedPdbName : WCHAR[256] + cVSig : DWORD + cVData : WCHAR[780] # MAX_PATH * 3 + pdbSig : DWORD + pdbSig70 : GUID + pdbAge : DWORD + pdbUnmatched : BOOL + dbgUnmatched : BOOL + lineNumbers : BOOL + globalSymbols : BOOL + typeInfo : BOOL + sourceIndexed : BOOL + publics : BOOL + machineType : DWORD + reserved : DWORD + end + + fun SymInitializeW(hProcess : HANDLE, userSearchPath : LPWSTR, fInvadeProcess : BOOL) : BOOL + fun SymCleanup(hProcess : HANDLE) : BOOL + fun SymGetOptions : DWORD + fun SymSetOptions(symOptions : DWORD) : DWORD + fun SymFromAddrW(hProcess : HANDLE, address : DWORD64, displacement : DWORD64*, symbol : SYMBOL_INFOW*) : BOOL + fun SymGetLineFromAddrW64(hProcess : HANDLE, dwAddr : DWORD64, pdwDisplacement : DWORD*, line : IMAGEHLP_LINEW64*) : BOOL + fun SymGetModuleInfoW64(hProcess : HANDLE, qwAddr : DWORD64, moduleInfo : IMAGEHLP_MODULEW64*) : BOOL + + # fun SymFunctionTableAccess64(hProcess : HANDLE, addrBase : DWORD64) : Void* + fun SymGetModuleBase64(hProcess : HANDLE, qwAddr : DWORD64) : DWORD64 + + enum ADDRESS_MODE + AddrMode1616 + AddrMode1632 + AddrModeReal + AddrModeFlat + end + + struct ADDRESS64 + offset : DWORD64 + segment : WORD + mode : ADDRESS_MODE + end + + struct KDHELP64 + thread : DWORD64 + thCallbackStack : DWORD + thCallbackBStore : DWORD + nextCallback : DWORD + framePointer : DWORD + kiCallUserMode : DWORD64 + keUserCallbackDispatcher : DWORD64 + systemRangeStart : DWORD64 + kiUserExceptionDispatcher : DWORD64 + stackBase : DWORD64 + stackLimit : DWORD64 + buildVersion : DWORD + retpolineStubFunctionTableSize : DWORD + retpolineStubFunctionTable : DWORD64 + retpolineStubOffset : DWORD + retpolineStubSize : DWORD + reserved0 : DWORD64[2] + end + + struct STACKFRAME64 + addrPC : ADDRESS64 + addrReturn : ADDRESS64 + addrFrame : ADDRESS64 + addrStack : ADDRESS64 + addrBStore : ADDRESS64 + funcTableEntry : Void* + params : DWORD64[4] + far : BOOL + virtual : BOOL + reserved : DWORD64[3] + kdHelp : KDHELP64 + end + + IMAGE_FILE_MACHINE_AMD64 = DWORD.new!(0x8664) + + alias PREAD_PROCESS_MEMORY_ROUTINE64 = HANDLE, DWORD64, Void*, DWORD, DWORD* -> BOOL + alias PFUNCTION_TABLE_ACCESS_ROUTINE64 = HANDLE, DWORD64 -> Void* + alias PGET_MODULE_BASE_ROUTINE64 = HANDLE, DWORD64 -> DWORD64 + alias PTRANSLATE_ADDRESS_ROUTINE64 = HANDLE, HANDLE, ADDRESS64* -> DWORD64 + + fun StackWalk64( + machineType : DWORD, hProcess : HANDLE, hThread : HANDLE, stackFrame : STACKFRAME64*, contextRecord : Void*, + readMemoryRoutine : PREAD_PROCESS_MEMORY_ROUTINE64, functionTableAccessRoutine : PFUNCTION_TABLE_ACCESS_ROUTINE64, + getModuleBaseRoutine : PGET_MODULE_BASE_ROUTINE64, translateAddress : PTRANSLATE_ADDRESS_ROUTINE64 + ) : BOOL +end diff --git a/src/lib_c/x86_64-windows-msvc/c/int_safe.cr b/src/lib_c/x86_64-windows-msvc/c/int_safe.cr index c415f981e943..0b9ea19eacd9 100644 --- a/src/lib_c/x86_64-windows-msvc/c/int_safe.cr +++ b/src/lib_c/x86_64-windows-msvc/c/int_safe.cr @@ -1,3 +1,4 @@ lib LibC alias DWORD = UInt32 + alias DWORD64 = UInt64 end diff --git a/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr b/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr index 5da638141581..ca76d7ca36cf 100644 --- a/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr +++ b/src/lib_c/x86_64-windows-msvc/c/processthreadsapi.cr @@ -32,6 +32,7 @@ lib LibC hStdError : HANDLE end + fun GetCurrentThread : HANDLE fun GetCurrentThreadStackLimits(lowLimit : ULONG_PTR*, highLimit : ULONG_PTR*) : Void fun GetCurrentProcess : HANDLE fun GetCurrentProcessId : DWORD 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 c20d77aba883..e910a217e1bd 100644 --- a/src/lib_c/x86_64-windows-msvc/c/winnt.cr +++ b/src/lib_c/x86_64-windows-msvc/c/winnt.cr @@ -78,4 +78,79 @@ lib LibC # (STANDARD_RIGHTS_WRITE | KEY_SET_VALUE | KEY_CREATE_SUB_KEY) & ~SYNCHRONIZE WRITE = 0x20006 end + + struct CONTEXT + p1Home : DWORD64 + p2Home : DWORD64 + p3Home : DWORD64 + p4Home : DWORD64 + p5Home : DWORD64 + p6Home : DWORD64 + contextFlags : DWORD + mxCsr : DWORD + segCs : WORD + segDs : WORD + segEs : WORD + segFs : WORD + segGs : WORD + segSs : WORD + eFlags : DWORD + dr0 : DWORD64 + dr1 : DWORD64 + dr2 : DWORD64 + dr3 : DWORD64 + dr6 : DWORD64 + dr7 : DWORD64 + rax : DWORD64 + rcx : DWORD64 + rdx : DWORD64 + rbx : DWORD64 + rsp : DWORD64 + rbp : DWORD64 + rsi : DWORD64 + rdi : DWORD64 + r8 : DWORD64 + r9 : DWORD64 + r10 : DWORD64 + r11 : DWORD64 + r12 : DWORD64 + r13 : DWORD64 + r14 : DWORD64 + r15 : DWORD64 + rip : DWORD64 + fltSave : UInt8[512] # DUMMYUNIONNAME + vectorRegister : UInt8[16][26] # M128A[26] + vectorControl : DWORD64 + debugControl : DWORD64 + lastBranchToRip : DWORD64 + lastBranchFromRip : DWORD64 + lastExceptionToRip : DWORD64 + lastExceptionFromRip : DWORD64 + end + + {% if flag?(:x86_64) %} + CONTEXT_AMD64 = DWORD.new!(0x00100000) + + CONTEXT_CONTROL = CONTEXT_AMD64 | 0x00000001 + CONTEXT_INTEGER = CONTEXT_AMD64 | 0x00000002 + CONTEXT_SEGMENTS = CONTEXT_AMD64 | 0x00000004 + CONTEXT_FLOATING_POINT = CONTEXT_AMD64 | 0x00000008 + CONTEXT_DEBUG_REGISTERS = CONTEXT_AMD64 | 0x00000010 + + CONTEXT_FULL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT + {% elsif flag?(:i386) %} + CONTEXT_i386 = DWORD.new!(0x00010000i64) + CONTEXT_i486 = DWORD.new!(0x00010000i64) + + CONTEXT_CONTROL = CONTEXT_i386 | 0x00000001 + CONTEXT_INTEGER = CONTEXT_i386 | 0x00000002 + CONTEXT_SEGMENTS = CONTEXT_i386 | 0x00000004 + CONTEXT_FLOATING_POINT = CONTEXT_i386 | 0x00000008 + CONTEXT_DEBUG_REGISTERS = CONTEXT_i386 | 0x00000010 + CONTEXT_EXTENDED_REGISTERS = CONTEXT_i386 | 0x00000020 + + CONTEXT_FULL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS + {% end %} + + fun RtlCaptureContext(contextRecord : CONTEXT*) end diff --git a/src/raise.cr b/src/raise.cr index b017e6a0b981..f5a23b23e8aa 100644 --- a/src/raise.cr +++ b/src/raise.cr @@ -111,6 +111,7 @@ end exception.inspect_with_backtrace(STDERR) {% end %} + exception.callstack ||= Exception::CallStack.new LibC._CxxThrowException(pointerof(exception).as(Void*), throw_info) end diff --git a/src/windows_stubs.cr b/src/windows_stubs.cr index 01783ab9e71b..47e465726f1a 100644 --- a/src/windows_stubs.cr +++ b/src/windows_stubs.cr @@ -1,11 +1,5 @@ require "c/synchapi" -struct Exception::CallStack - def self.skip(*args) - # do nothing - end -end - abstract class IO private class Encoder def initialize(@encoding_options : EncodingOptions)