Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support call stacks for MinGW-w64 builds
Browse files Browse the repository at this point in the history
HertzDevil committed Oct 22, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent 5f72133 commit c574066
Showing 11 changed files with 369 additions and 109 deletions.
6 changes: 3 additions & 3 deletions spec/std/exception/call_stack_spec.cr
Original file line number Diff line number Diff line change
@@ -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 %}
100 changes: 100 additions & 0 deletions src/crystal/pe.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
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

getter original_image_base : UInt64
@section_headers : Slice(SectionHeader)
@string_table_base : UInt32
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] === '/'
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 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

section_coff_symbols = @coff_symbols.put_if_absent(sym.sectionNumber.to_i &- 1) { [] of COFFSymbol }
section_coff_symbols << COFFSymbol.new(sym.value, name)
end

@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
44 changes: 44 additions & 0 deletions src/crystal/system/win32/signal.cr
Original file line number Diff line number Diff line change
@@ -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
5 changes: 1 addition & 4 deletions src/exception/call_stack.cr
Original file line number Diff line number Diff line change
@@ -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 %}
4 changes: 4 additions & 0 deletions src/exception/call_stack/dwarf.cr
Original file line number Diff line number Diff line change
@@ -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"
86 changes: 52 additions & 34 deletions src/exception/call_stack/elf.cr
Original file line number Diff line number Diff line change
@@ -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

113 changes: 106 additions & 7 deletions src/exception/call_stack/libunwind.cr
Original file line number Diff line number Diff line change
@@ -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
61 changes: 1 addition & 60 deletions src/exception/call_stack/stackwalk.cr
Original file line number Diff line number Diff line change
@@ -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)
3 changes: 3 additions & 0 deletions src/lib_c/x86_64-windows-msvc/c/libloaderapi.cr
Original file line number Diff line number Diff line change
@@ -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
54 changes: 54 additions & 0 deletions src/lib_c/x86_64-windows-msvc/c/winnt.cr
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion src/raise.cr
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit c574066

Please sign in to comment.