mirror of
https://github.com/nim-lang/Nim.git
synced 2026-01-03 03:32:32 +00:00
* nimStackTraceOverride: enable stack traces in exceptions
This is a two-step stack trace collection scheme, because re-raised
exceptions will collect multiple stack traces but use them rarely, when
printing info about an uncaught exception, so it makes sense to only do
the cheap stack unwinding all the time and the relatively expensive
debugging information collection on-demand.
`asyncfutures` implements its own `$` proc for printing
`seq[StackTraceEntry]`, so we have to add the debugging info there, just
like we do for the private `$` proc in `system/excpt`.
* cleaned up PR #15284
Co-authored-by: Ștefan Talpalaru <stefantalpalaru@yahoo.com>
(cherry picked from commit 1fae66e4df)
644 lines
20 KiB
Nim
644 lines
20 KiB
Nim
#
|
|
#
|
|
# Nim's Runtime Library
|
|
# (c) Copyright 2015 Andreas Rumpf
|
|
#
|
|
# See the file "copying.txt", included in this
|
|
# distribution, for details about the copyright.
|
|
#
|
|
|
|
# Exception handling code. Carefully coded so that tiny programs which do not
|
|
# use the heap (and nor exceptions) do not include the GC or memory allocator.
|
|
|
|
import stacktraces
|
|
|
|
var
|
|
errorMessageWriter*: (proc(msg: string) {.tags: [WriteIOEffect], benign,
|
|
nimcall.})
|
|
## Function that will be called
|
|
## instead of `stdmsg.write` when printing stacktrace.
|
|
## Unstable API.
|
|
|
|
when not defined(windows) or not defined(guiapp):
|
|
proc writeToStdErr(msg: cstring) = rawWrite(cstderr, msg)
|
|
|
|
else:
|
|
proc MessageBoxA(hWnd: pointer, lpText, lpCaption: cstring, uType: int): int32 {.
|
|
header: "<windows.h>", nodecl.}
|
|
|
|
proc writeToStdErr(msg: cstring) =
|
|
discard MessageBoxA(nil, msg, nil, 0)
|
|
|
|
proc showErrorMessage(data: cstring) {.gcsafe, raises: [].} =
|
|
var toWrite = true
|
|
if errorMessageWriter != nil:
|
|
try:
|
|
errorMessageWriter($data)
|
|
toWrite = false
|
|
except:
|
|
discard
|
|
if toWrite:
|
|
when defined(genode):
|
|
# stderr not available by default, use the LOG session
|
|
echo data
|
|
else:
|
|
writeToStdErr(data)
|
|
|
|
proc chckIndx(i, a, b: int): int {.inline, compilerproc, benign.}
|
|
proc chckRange(i, a, b: int): int {.inline, compilerproc, benign.}
|
|
proc chckRangeF(x, a, b: float): float {.inline, compilerproc, benign.}
|
|
proc chckNil(p: pointer) {.noinline, compilerproc, benign.}
|
|
|
|
type
|
|
GcFrame = ptr GcFrameHeader
|
|
GcFrameHeader {.compilerproc.} = object
|
|
len: int
|
|
prev: ptr GcFrameHeader
|
|
|
|
when NimStackTraceMsgs:
|
|
var frameMsgBuf* {.threadvar.}: string
|
|
var
|
|
framePtr {.threadvar.}: PFrame
|
|
excHandler {.threadvar.}: PSafePoint
|
|
# list of exception handlers
|
|
# a global variable for the root of all try blocks
|
|
currException {.threadvar.}: ref Exception
|
|
gcFramePtr {.threadvar.}: GcFrame
|
|
|
|
type
|
|
FrameState = tuple[gcFramePtr: GcFrame, framePtr: PFrame,
|
|
excHandler: PSafePoint, currException: ref Exception]
|
|
|
|
proc getFrameState*(): FrameState {.compilerRtl, inl.} =
|
|
return (gcFramePtr, framePtr, excHandler, currException)
|
|
|
|
proc setFrameState*(state: FrameState) {.compilerRtl, inl.} =
|
|
gcFramePtr = state.gcFramePtr
|
|
framePtr = state.framePtr
|
|
excHandler = state.excHandler
|
|
currException = state.currException
|
|
|
|
proc getFrame*(): PFrame {.compilerRtl, inl.} = framePtr
|
|
|
|
proc popFrame {.compilerRtl, inl.} =
|
|
framePtr = framePtr.prev
|
|
|
|
when false:
|
|
proc popFrameOfAddr(s: PFrame) {.compilerRtl.} =
|
|
var it = framePtr
|
|
if it == s:
|
|
framePtr = framePtr.prev
|
|
else:
|
|
while it != nil:
|
|
if it == s:
|
|
framePtr = it.prev
|
|
break
|
|
it = it.prev
|
|
|
|
proc setFrame*(s: PFrame) {.compilerRtl, inl.} =
|
|
framePtr = s
|
|
|
|
proc getGcFrame*(): GcFrame {.compilerRtl, inl.} = gcFramePtr
|
|
proc popGcFrame*() {.compilerRtl, inl.} = gcFramePtr = gcFramePtr.prev
|
|
proc setGcFrame*(s: GcFrame) {.compilerRtl, inl.} = gcFramePtr = s
|
|
proc pushGcFrame*(s: GcFrame) {.compilerRtl, inl.} =
|
|
s.prev = gcFramePtr
|
|
zeroMem(cast[pointer](cast[int](s)+%sizeof(GcFrameHeader)), s.len*sizeof(pointer))
|
|
gcFramePtr = s
|
|
|
|
proc pushSafePoint(s: PSafePoint) {.compilerRtl, inl.} =
|
|
s.prev = excHandler
|
|
excHandler = s
|
|
|
|
proc popSafePoint {.compilerRtl, inl.} =
|
|
excHandler = excHandler.prev
|
|
|
|
proc pushCurrentException(e: sink(ref Exception)) {.compilerRtl, inl.} =
|
|
e.up = currException
|
|
currException = e
|
|
#showErrorMessage "A"
|
|
|
|
proc popCurrentException {.compilerRtl, inl.} =
|
|
currException = currException.up
|
|
#showErrorMessage "B"
|
|
|
|
proc popCurrentExceptionEx(id: uint) {.compilerRtl.} =
|
|
discard "only for bootstrapping compatbility"
|
|
|
|
proc closureIterSetupExc(e: ref Exception) {.compilerproc, inline.} =
|
|
currException = e
|
|
|
|
# some platforms have native support for stack traces:
|
|
const
|
|
nativeStackTraceSupported* = (defined(macosx) or defined(linux)) and
|
|
not NimStackTrace
|
|
hasSomeStackTrace = NimStackTrace or defined(nimStackTraceOverride) or
|
|
(defined(nativeStackTrace) and nativeStackTraceSupported)
|
|
|
|
|
|
when defined(nativeStacktrace) and nativeStackTraceSupported:
|
|
type
|
|
TDl_info {.importc: "Dl_info", header: "<dlfcn.h>",
|
|
final, pure.} = object
|
|
dli_fname: cstring
|
|
dli_fbase: pointer
|
|
dli_sname: cstring
|
|
dli_saddr: pointer
|
|
|
|
proc backtrace(symbols: ptr pointer, size: int): int {.
|
|
importc: "backtrace", header: "<execinfo.h>".}
|
|
proc dladdr(addr1: pointer, info: ptr TDl_info): int {.
|
|
importc: "dladdr", header: "<dlfcn.h>".}
|
|
|
|
when not hasThreadSupport:
|
|
var
|
|
tempAddresses: array[maxStackTraceLines, pointer] # should not be alloc'd on stack
|
|
tempDlInfo: TDl_info
|
|
|
|
proc auxWriteStackTraceWithBacktrace(s: var string) =
|
|
when hasThreadSupport:
|
|
var
|
|
tempAddresses: array[maxStackTraceLines, pointer] # but better than a threadvar
|
|
tempDlInfo: TDl_info
|
|
# This is allowed to be expensive since it only happens during crashes
|
|
# (but this way you don't need manual stack tracing)
|
|
var size = backtrace(cast[ptr pointer](addr(tempAddresses)),
|
|
len(tempAddresses))
|
|
var enabled = false
|
|
for i in 0..size-1:
|
|
var dlresult = dladdr(tempAddresses[i], addr(tempDlInfo))
|
|
if enabled:
|
|
if dlresult != 0:
|
|
var oldLen = s.len
|
|
add(s, tempDlInfo.dli_fname)
|
|
if tempDlInfo.dli_sname != nil:
|
|
for k in 1..max(1, 25-(s.len-oldLen)): add(s, ' ')
|
|
add(s, tempDlInfo.dli_sname)
|
|
else:
|
|
add(s, '?')
|
|
add(s, "\n")
|
|
else:
|
|
if dlresult != 0 and tempDlInfo.dli_sname != nil and
|
|
c_strcmp(tempDlInfo.dli_sname, "signalHandler") == 0'i32:
|
|
# Once we're past signalHandler, we're at what the user is
|
|
# interested in
|
|
enabled = true
|
|
|
|
when hasSomeStackTrace and not hasThreadSupport:
|
|
var
|
|
tempFrames: array[maxStackTraceLines, PFrame] # should not be alloc'd on stack
|
|
|
|
template reraisedFrom(z): untyped =
|
|
StackTraceEntry(procname: nil, line: z, filename: nil)
|
|
|
|
proc auxWriteStackTrace(f: PFrame; s: var seq[StackTraceEntry]) =
|
|
var
|
|
it = f
|
|
i = 0
|
|
while it != nil:
|
|
inc(i)
|
|
it = it.prev
|
|
var last = i-1
|
|
when true: # not defined(gcDestructors):
|
|
if s.len == 0:
|
|
s = newSeq[StackTraceEntry](i)
|
|
else:
|
|
last = s.len + i - 1
|
|
s.setLen(last+1)
|
|
it = f
|
|
while it != nil:
|
|
s[last] = StackTraceEntry(procname: it.procname,
|
|
line: it.line,
|
|
filename: it.filename)
|
|
when NimStackTraceMsgs:
|
|
let first = if it.prev == nil: 0 else: it.prev.frameMsgLen
|
|
if it.frameMsgLen > first:
|
|
s[last].frameMsg.setLen(it.frameMsgLen - first)
|
|
# somehow string slicing not available here
|
|
for i in first .. it.frameMsgLen-1:
|
|
s[last].frameMsg[i-first] = frameMsgBuf[i]
|
|
it = it.prev
|
|
dec last
|
|
|
|
template addFrameEntry(s: var string, f: StackTraceEntry|PFrame) =
|
|
var oldLen = s.len
|
|
add(s, f.filename)
|
|
if f.line > 0:
|
|
add(s, '(')
|
|
add(s, $f.line)
|
|
add(s, ')')
|
|
for k in 1..max(1, 25-(s.len-oldLen)): add(s, ' ')
|
|
add(s, f.procname)
|
|
when NimStackTraceMsgs:
|
|
when type(f) is StackTraceEntry:
|
|
add(s, f.frameMsg)
|
|
else:
|
|
var first = if f.prev == nil: 0 else: f.prev.frameMsgLen
|
|
for i in first..<f.frameMsgLen: add(s, frameMsgBuf[i])
|
|
add(s, "\n")
|
|
|
|
proc `$`(stackTraceEntries: seq[StackTraceEntry]): string =
|
|
when defined(nimStackTraceOverride):
|
|
let s = addDebuggingInfo(stackTraceEntries)
|
|
else:
|
|
let s = stackTraceEntries
|
|
|
|
result = newStringOfCap(2000)
|
|
for i in 0 .. s.len-1:
|
|
if s[i].line == reraisedFromBegin: result.add "[[reraised from:\n"
|
|
elif s[i].line == reraisedFromEnd: result.add "]]\n"
|
|
else: addFrameEntry(result, s[i])
|
|
|
|
when hasSomeStackTrace:
|
|
|
|
proc auxWriteStackTrace(f: PFrame, s: var string) =
|
|
when hasThreadSupport:
|
|
var
|
|
tempFrames: array[maxStackTraceLines, PFrame] # but better than a threadvar
|
|
const
|
|
firstCalls = 32
|
|
var
|
|
it = f
|
|
i = 0
|
|
total = 0
|
|
# setup long head:
|
|
while it != nil and i <= high(tempFrames)-firstCalls:
|
|
tempFrames[i] = it
|
|
inc(i)
|
|
inc(total)
|
|
it = it.prev
|
|
# go up the stack to count 'total':
|
|
var b = it
|
|
while it != nil:
|
|
inc(total)
|
|
it = it.prev
|
|
var skipped = 0
|
|
if total > len(tempFrames):
|
|
# skip N
|
|
skipped = total-i-firstCalls+1
|
|
for j in 1..skipped:
|
|
if b != nil: b = b.prev
|
|
# create '...' entry:
|
|
tempFrames[i] = nil
|
|
inc(i)
|
|
# setup short tail:
|
|
while b != nil and i <= high(tempFrames):
|
|
tempFrames[i] = b
|
|
inc(i)
|
|
b = b.prev
|
|
for j in countdown(i-1, 0):
|
|
if tempFrames[j] == nil:
|
|
add(s, "(")
|
|
add(s, $skipped)
|
|
add(s, " calls omitted) ...\n")
|
|
else:
|
|
addFrameEntry(s, tempFrames[j])
|
|
|
|
proc stackTraceAvailable*(): bool
|
|
|
|
proc rawWriteStackTrace(s: var string) =
|
|
when defined(nimStackTraceOverride):
|
|
add(s, "Traceback (most recent call last, using override)\n")
|
|
auxWriteStackTraceWithOverride(s)
|
|
elif NimStackTrace:
|
|
if framePtr == nil:
|
|
add(s, "No stack traceback available\n")
|
|
else:
|
|
add(s, "Traceback (most recent call last)\n")
|
|
auxWriteStackTrace(framePtr, s)
|
|
elif defined(nativeStackTrace) and nativeStackTraceSupported:
|
|
add(s, "Traceback from system (most recent call last)\n")
|
|
auxWriteStackTraceWithBacktrace(s)
|
|
else:
|
|
add(s, "No stack traceback available\n")
|
|
|
|
proc rawWriteStackTrace(s: var seq[StackTraceEntry]) =
|
|
when defined(nimStackTraceOverride):
|
|
auxWriteStackTraceWithOverride(s)
|
|
elif NimStackTrace:
|
|
auxWriteStackTrace(framePtr, s)
|
|
else:
|
|
s = @[]
|
|
|
|
proc stackTraceAvailable(): bool =
|
|
when defined(nimStackTraceOverride):
|
|
result = true
|
|
elif NimStackTrace:
|
|
if framePtr == nil:
|
|
result = false
|
|
else:
|
|
result = true
|
|
elif defined(nativeStackTrace) and nativeStackTraceSupported:
|
|
result = true
|
|
else:
|
|
result = false
|
|
else:
|
|
proc stackTraceAvailable*(): bool = result = false
|
|
|
|
var onUnhandledException*: (proc (errorMsg: string) {.
|
|
nimcall.}) ## Set this error \
|
|
## handler to override the existing behaviour on an unhandled exception.
|
|
##
|
|
## The default is to write a stacktrace to ``stderr`` and then call ``quit(1)``.
|
|
## Unstable API.
|
|
|
|
proc reportUnhandledErrorAux(e: ref Exception) {.nodestroy.} =
|
|
when hasSomeStackTrace:
|
|
var buf = newStringOfCap(2000)
|
|
if e.trace.len == 0:
|
|
rawWriteStackTrace(buf)
|
|
else:
|
|
var trace = $e.trace
|
|
add(buf, trace)
|
|
`=destroy`(trace)
|
|
add(buf, "Error: unhandled exception: ")
|
|
add(buf, e.msg)
|
|
add(buf, " [")
|
|
add(buf, $e.name)
|
|
add(buf, "]\n")
|
|
|
|
if onUnhandledException != nil:
|
|
onUnhandledException(buf)
|
|
else:
|
|
showErrorMessage(buf)
|
|
`=destroy`(buf)
|
|
else:
|
|
# ugly, but avoids heap allocations :-)
|
|
template xadd(buf, s, slen) =
|
|
if L + slen < high(buf):
|
|
copyMem(addr(buf[L]), cstring(s), slen)
|
|
inc L, slen
|
|
template add(buf, s) =
|
|
xadd(buf, s, s.len)
|
|
var buf: array[0..2000, char]
|
|
var L = 0
|
|
if e.trace.len != 0:
|
|
var trace = $e.trace
|
|
add(buf, trace)
|
|
`=destroy`(trace)
|
|
add(buf, "Error: unhandled exception: ")
|
|
add(buf, e.msg)
|
|
add(buf, " [")
|
|
xadd(buf, e.name, e.name.len)
|
|
add(buf, "]\n")
|
|
when defined(nimNoArrayToCstringConversion):
|
|
template tbuf(): untyped = addr buf
|
|
else:
|
|
template tbuf(): untyped = buf
|
|
|
|
if onUnhandledException != nil:
|
|
onUnhandledException($tbuf())
|
|
else:
|
|
showErrorMessage(tbuf())
|
|
|
|
proc reportUnhandledError(e: ref Exception) {.nodestroy.} =
|
|
if unhandledExceptionHook != nil:
|
|
unhandledExceptionHook(e)
|
|
when hostOS != "any":
|
|
reportUnhandledErrorAux(e)
|
|
else:
|
|
discard()
|
|
|
|
proc nimLeaveFinally() {.compilerRtl.} =
|
|
when defined(cpp) and not defined(noCppExceptions) and not gotoBasedExceptions:
|
|
{.emit: "throw;".}
|
|
else:
|
|
if excHandler != nil:
|
|
c_longjmp(excHandler.context, 1)
|
|
else:
|
|
reportUnhandledError(currException)
|
|
quit(1)
|
|
|
|
when gotoBasedExceptions:
|
|
var nimInErrorMode {.threadvar.}: bool
|
|
|
|
proc nimErrorFlag(): ptr bool {.compilerRtl, inl.} =
|
|
result = addr(nimInErrorMode)
|
|
|
|
proc nimTestErrorFlag() {.compilerRtl.} =
|
|
## This proc must be called before ``currException`` is destroyed.
|
|
## It also must be called at the end of every thread to ensure no
|
|
## error is swallowed.
|
|
if nimInErrorMode and currException != nil:
|
|
reportUnhandledError(currException)
|
|
currException = nil
|
|
quit(1)
|
|
|
|
proc raiseExceptionAux(e: sink(ref Exception)) {.nodestroy.} =
|
|
if localRaiseHook != nil:
|
|
if not localRaiseHook(e): return
|
|
if globalRaiseHook != nil:
|
|
if not globalRaiseHook(e): return
|
|
when defined(cpp) and not defined(noCppExceptions) and not gotoBasedExceptions:
|
|
if e == currException:
|
|
{.emit: "throw;".}
|
|
else:
|
|
pushCurrentException(e)
|
|
{.emit: "throw e;".}
|
|
elif defined(nimQuirky) or gotoBasedExceptions:
|
|
# XXX This check should likely also be done in the setjmp case below.
|
|
if e != currException:
|
|
pushCurrentException(e)
|
|
when gotoBasedExceptions:
|
|
inc nimInErrorMode
|
|
else:
|
|
if excHandler != nil:
|
|
pushCurrentException(e)
|
|
c_longjmp(excHandler.context, 1)
|
|
else:
|
|
reportUnhandledError(e)
|
|
quit(1)
|
|
|
|
proc raiseExceptionEx(e: sink(ref Exception), ename, procname, filename: cstring,
|
|
line: int) {.compilerRtl, nodestroy.} =
|
|
if e.name.isNil: e.name = ename
|
|
when hasSomeStackTrace:
|
|
when defined(nimStackTraceOverride):
|
|
if e.trace.len == 0:
|
|
rawWriteStackTrace(e.trace)
|
|
else:
|
|
e.trace.add reraisedFrom(reraisedFromBegin)
|
|
auxWriteStackTraceWithOverride(e.trace)
|
|
e.trace.add reraisedFrom(reraisedFromEnd)
|
|
elif NimStackTrace:
|
|
if e.trace.len == 0:
|
|
rawWriteStackTrace(e.trace)
|
|
elif framePtr != nil:
|
|
e.trace.add reraisedFrom(reraisedFromBegin)
|
|
auxWriteStackTrace(framePtr, e.trace)
|
|
e.trace.add reraisedFrom(reraisedFromEnd)
|
|
else:
|
|
if procname != nil and filename != nil:
|
|
e.trace.add StackTraceEntry(procname: procname, filename: filename, line: line)
|
|
raiseExceptionAux(e)
|
|
|
|
proc raiseException(e: sink(ref Exception), ename: cstring) {.compilerRtl.} =
|
|
raiseExceptionEx(e, ename, nil, nil, 0)
|
|
|
|
proc reraiseException() {.compilerRtl.} =
|
|
if currException == nil:
|
|
sysFatal(ReraiseError, "no exception to reraise")
|
|
else:
|
|
when gotoBasedExceptions:
|
|
inc nimInErrorMode
|
|
else:
|
|
raiseExceptionAux(currException)
|
|
|
|
proc writeStackTrace() =
|
|
when hasSomeStackTrace:
|
|
var s = ""
|
|
rawWriteStackTrace(s)
|
|
cast[proc (s: cstring) {.noSideEffect, tags: [], nimcall, raises: [].}](showErrorMessage)(s)
|
|
else:
|
|
cast[proc (s: cstring) {.noSideEffect, tags: [], nimcall, raises: [].}](showErrorMessage)("No stack traceback available\n")
|
|
|
|
proc getStackTrace(): string =
|
|
when hasSomeStackTrace:
|
|
result = ""
|
|
rawWriteStackTrace(result)
|
|
else:
|
|
result = "No stack traceback available\n"
|
|
|
|
proc getStackTrace(e: ref Exception): string =
|
|
if not isNil(e):
|
|
result = $e.trace
|
|
else:
|
|
result = ""
|
|
|
|
proc getStackTraceEntries*(e: ref Exception): seq[StackTraceEntry] =
|
|
## Returns the attached stack trace to the exception ``e`` as
|
|
## a ``seq``. This is not yet available for the JS backend.
|
|
when not defined(nimSeqsV2):
|
|
shallowCopy(result, e.trace)
|
|
else:
|
|
result = move(e.trace)
|
|
|
|
proc getStackTraceEntries*(): seq[StackTraceEntry] =
|
|
## Returns the stack trace entries for the current stack trace.
|
|
## This is not yet available for the JS backend.
|
|
when hasSomeStackTrace:
|
|
rawWriteStackTrace(result)
|
|
|
|
const nimCallDepthLimit {.intdefine.} = 2000
|
|
|
|
proc callDepthLimitReached() {.noinline.} =
|
|
writeStackTrace()
|
|
showErrorMessage("Error: call depth limit reached in a debug build (" &
|
|
$nimCallDepthLimit & " function calls). You can change it with " &
|
|
"-d:nimCallDepthLimit=<int> but really try to avoid deep " &
|
|
"recursions instead.\n")
|
|
quit(1)
|
|
|
|
proc nimFrame(s: PFrame) {.compilerRtl, inl, raises: [].} =
|
|
if framePtr == nil:
|
|
s.calldepth = 0
|
|
when NimStackTraceMsgs: s.frameMsgLen = 0
|
|
else:
|
|
s.calldepth = framePtr.calldepth+1
|
|
when NimStackTraceMsgs: s.frameMsgLen = framePtr.frameMsgLen
|
|
s.prev = framePtr
|
|
framePtr = s
|
|
if s.calldepth == nimCallDepthLimit: callDepthLimitReached()
|
|
|
|
when defined(cpp) and appType != "lib" and not gotoBasedExceptions and
|
|
not defined(js) and not defined(nimscript) and
|
|
hostOS != "standalone" and not defined(noCppExceptions):
|
|
|
|
type
|
|
StdException {.importcpp: "std::exception", header: "<exception>".} = object
|
|
|
|
proc what(ex: StdException): cstring {.importcpp: "((char *)#.what())".}
|
|
|
|
proc setTerminate(handler: proc() {.noconv.})
|
|
{.importc: "std::set_terminate", header: "<exception>".}
|
|
|
|
setTerminate proc() {.noconv.} =
|
|
# Remove ourself as a handler, reinstalling the default handler.
|
|
setTerminate(nil)
|
|
|
|
var msg = "Unknown error in unexpected exception handler"
|
|
try:
|
|
{.emit: "#if !defined(_MSC_VER) || (_MSC_VER >= 1923)".}
|
|
raise
|
|
{.emit: "#endif".}
|
|
except Exception:
|
|
msg = currException.getStackTrace() & "Error: unhandled exception: " &
|
|
currException.msg & " [" & $currException.name & "]"
|
|
except StdException as e:
|
|
msg = "Error: unhandled cpp exception: " & $e.what()
|
|
except:
|
|
msg = "Error: unhandled unknown cpp exception"
|
|
|
|
{.emit: "#if defined(_MSC_VER) && (_MSC_VER < 1923)".}
|
|
msg = "Error: unhandled unknown cpp exception"
|
|
{.emit: "#endif".}
|
|
|
|
when defined(genode):
|
|
# stderr not available by default, use the LOG session
|
|
echo msg
|
|
else:
|
|
writeToStdErr msg & "\n"
|
|
|
|
quit 1
|
|
|
|
when not defined(noSignalHandler) and not defined(useNimRtl):
|
|
proc signalHandler(sign: cint) {.exportc: "signalHandler", noconv.} =
|
|
template processSignal(s, action: untyped) {.dirty.} =
|
|
if s == SIGINT: action("SIGINT: Interrupted by Ctrl-C.\n")
|
|
elif s == SIGSEGV:
|
|
action("SIGSEGV: Illegal storage access. (Attempt to read from nil?)\n")
|
|
elif s == SIGABRT:
|
|
action("SIGABRT: Abnormal termination.\n")
|
|
elif s == SIGFPE: action("SIGFPE: Arithmetic error.\n")
|
|
elif s == SIGILL: action("SIGILL: Illegal operation.\n")
|
|
elif (when declared(SIGBUS): s == SIGBUS else: false):
|
|
action("SIGBUS: Illegal storage access. (Attempt to read from nil?)\n")
|
|
else:
|
|
block platformSpecificSignal:
|
|
when declared(SIGPIPE):
|
|
if s == SIGPIPE:
|
|
action("SIGPIPE: Pipe closed.\n")
|
|
break platformSpecificSignal
|
|
action("unknown signal\n")
|
|
|
|
# print stack trace and quit
|
|
when defined(memtracker):
|
|
logPendingOps()
|
|
when hasSomeStackTrace:
|
|
when not usesDestructors: GC_disable()
|
|
var buf = newStringOfCap(2000)
|
|
rawWriteStackTrace(buf)
|
|
processSignal(sign, buf.add) # nice hu? currying a la Nim :-)
|
|
showErrorMessage(buf)
|
|
when not usesDestructors: GC_enable()
|
|
else:
|
|
var msg: cstring
|
|
template asgn(y) =
|
|
msg = y
|
|
processSignal(sign, asgn)
|
|
showErrorMessage(msg)
|
|
quit(1) # always quit when SIGABRT
|
|
|
|
proc registerSignalHandler() =
|
|
c_signal(SIGINT, signalHandler)
|
|
c_signal(SIGSEGV, signalHandler)
|
|
c_signal(SIGABRT, signalHandler)
|
|
c_signal(SIGFPE, signalHandler)
|
|
c_signal(SIGILL, signalHandler)
|
|
when declared(SIGBUS):
|
|
c_signal(SIGBUS, signalHandler)
|
|
when declared(SIGPIPE):
|
|
c_signal(SIGPIPE, signalHandler)
|
|
|
|
registerSignalHandler() # call it in initialization section
|
|
|
|
proc setControlCHook(hook: proc () {.noconv.}) =
|
|
# ugly cast, but should work on all architectures:
|
|
type SignalHandler = proc (sign: cint) {.noconv, benign.}
|
|
c_signal(SIGINT, cast[SignalHandler](hook))
|
|
|
|
when not defined(noSignalHandler) and not defined(useNimRtl):
|
|
proc unsetControlCHook() =
|
|
# proc to unset a hook set by setControlCHook
|
|
c_signal(SIGINT, signalHandler)
|