summary refs log tree commit diff stats
path: root/lib/system/excpt.nim
diff options
context:
space:
mode:
Diffstat (limited to 'lib/system/excpt.nim')
-rw-r--r--lib/system/excpt.nim681
1 files changed, 507 insertions, 174 deletions
diff --git a/lib/system/excpt.nim b/lib/system/excpt.nim
index df28c1493..dae5c4a4a 100644
--- a/lib/system/excpt.nim
+++ b/lib/system/excpt.nim
@@ -10,69 +10,167 @@
 # 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 std/private/miscdollars
+import stacktraces
+
+const noStacktraceAvailable = "No stack traceback available\n"
+
 var
-  errorMessageWriter*: (proc(msg: string) {.tags: [WriteIOEffect], benign.})
+  errorMessageWriter*: (proc(msg: string) {.tags: [WriteIOEffect], benign,
+                                            nimcall.})
     ## Function that will be called
-    ## instead of stdmsg.write when printing stacktrace.
+    ## instead of `stdmsg.write` when printing stacktrace.
     ## Unstable API.
 
-when not defined(windows) or not defined(guiapp):
-  proc writeToStdErr(msg: cstring) = write(stdmsg, msg)
+when defined(windows):
+  proc GetLastError(): int32 {.header: "<windows.h>", nodecl.}
+  const ERROR_BAD_EXE_FORMAT = 193
 
+when not defined(windows) or not defined(guiapp):
+  proc writeToStdErr(msg: cstring) = rawWrite(cstderr, msg)
+  proc writeToStdErr(msg: cstring, length: int) =
+    rawWriteString(cstderr, msg, length)
 else:
-  proc MessageBoxA(hWnd: cint, lpText, lpCaption: cstring, uType: int): int32 {.
+  proc MessageBoxA(hWnd: pointer, lpText, lpCaption: cstring, uType: int): int32 {.
     header: "<windows.h>", nodecl.}
-
   proc writeToStdErr(msg: cstring) =
-    discard MessageBoxA(0, msg, nil, 0)
+    discard MessageBoxA(nil, msg, nil, 0)
+  proc writeToStdErr(msg: cstring, length: int) =
+    discard MessageBoxA(nil, msg, nil, 0)
+
+proc writeToStdErr(msg: string) {.inline.} =
+  # fix bug #13115: handles correctly '\0' unlike default implicit conversion to cstring
+  writeToStdErr(msg.cstring, msg.len)
 
-proc showErrorMessage(data: cstring) =
+proc showErrorMessage(data: cstring, length: int) {.gcsafe, raises: [].} =
+  var toWrite = true
   if errorMessageWriter != nil:
-    errorMessageWriter($data)
-  else:
-    writeToStdErr(data)
+    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, length)
+
+proc showErrorMessage2(data: string) {.inline.} =
+  showErrorMessage(data.cstring, data.len)
 
 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
+
+when not defined(nimV2):
+  var
+    framePtr {.threadvar.}: PFrame
+
 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
 
+when not gotoBasedExceptions:
+  var
+    excHandler {.threadvar.}: PSafePoint
+      # list of exception handlers
+      # a global variable for the root of all try blocks
+    gcFramePtr {.threadvar.}: GcFrame
+
+when gotoBasedExceptions:
+  type
+    FrameState = tuple[framePtr: PFrame,
+                      currException: ref Exception]
+else:
+  type
+    FrameState = tuple[gcFramePtr: GcFrame, framePtr: PFrame,
+                      excHandler: PSafePoint, currException: ref Exception]
+
+proc getFrameState*(): FrameState {.compilerRtl, inl.} =
+  when gotoBasedExceptions:
+    return (framePtr, currException)
+  else:
+    return (gcFramePtr, framePtr, excHandler, currException)
+
+proc setFrameState*(state: FrameState) {.compilerRtl, inl.} =
+  when gotoBasedExceptions:
+    framePtr = state.framePtr
+    currException = state.currException
+  else:
+    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 pushSafePoint(s: PSafePoint) {.compilerRtl, inl.} =
-  s.hasRaiseAction = false
-  s.prev = excHandler
-  excHandler = s
+when not gotoBasedExceptions:
+  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 popSafePoint {.compilerRtl, inl.} =
-  excHandler = excHandler.prev
+  proc pushSafePoint(s: PSafePoint) {.compilerRtl, inl.} =
+    s.prev = excHandler
+    excHandler = s
 
-proc pushCurrentException(e: ref Exception) {.compilerRtl, inl.} =
-  e.parent = currException
+  proc popSafePoint {.compilerRtl, inl.} =
+    excHandler = excHandler.prev
+
+proc pushCurrentException(e: sink(ref Exception)) {.compilerRtl, inl.} =
+  e.up = currException
   currException = e
+  #showErrorMessage2 "A"
 
 proc popCurrentException {.compilerRtl, inl.} =
-  currException = nil # currException.parent
+  currException = currException.up
+  #showErrorMessage2 "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
+  nativeStackTraceSupported = (defined(macosx) or defined(linux)) and
                               not NimStackTrace
-  hasSomeStackTrace = NimStackTrace or
-    defined(nativeStackTrace) and nativeStackTraceSupported
+  hasSomeStackTrace = NimStackTrace or defined(nimStackTraceOverride) or
+    (defined(nativeStackTrace) and nativeStackTraceSupported)
+
 
 when defined(nativeStacktrace) and nativeStackTraceSupported:
   type
@@ -90,13 +188,13 @@ when defined(nativeStacktrace) and nativeStackTraceSupported:
 
   when not hasThreadSupport:
     var
-      tempAddresses: array [0..127, pointer] # should not be alloc'd on stack
+      tempAddresses: array[maxStackTraceLines, pointer] # should not be alloc'd on stack
       tempDlInfo: TDl_info
 
   proc auxWriteStackTraceWithBacktrace(s: var string) =
     when hasThreadSupport:
       var
-        tempAddresses: array [0..127, pointer] # but better than a threadvar
+        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)
@@ -122,68 +220,121 @@ when defined(nativeStacktrace) and nativeStackTraceSupported:
           # interested in
           enabled = true
 
-when not hasThreadSupport:
+when hasSomeStackTrace and not hasThreadSupport:
   var
-    tempFrames: array [0..127, PFrame] # should not be alloc'd on stack
+    tempFrames: array[maxStackTraceLines, PFrame] # should not be alloc'd on stack
 
-proc auxWriteStackTrace(f: PFrame, s: var string) =
-  when hasThreadSupport:
-    var
-      tempFrames: array [0..127, PFrame] # but better than a threadvar
-  const
-    firstCalls = 32
+template reraisedFrom(z): untyped =
+  StackTraceEntry(procname: nil, line: z, filename: nil)
+
+proc auxWriteStackTrace(f: PFrame; s: var seq[StackTraceEntry]) =
   var
     it = f
     i = 0
-    total = 0
-  # setup long head:
-  while it != nil and i <= high(tempFrames)-firstCalls:
-    tempFrames[i] = it
+  while it != nil:
     inc(i)
-    inc(total)
     it = it.prev
-  # go up the stack to count 'total':
-  var b = it
+  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:
-    inc(total)
+    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
-  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) ...")
+    dec last
+
+template addFrameEntry(s: var string, f: StackTraceEntry|PFrame) =
+  var oldLen = s.len
+  s.toLocation(f.filename, f.line, 0)
+  for k in 1..max(1, 25-(s.len-oldLen)): add(s, ' ')
+  add(s, f.procname)
+  when NimStackTraceMsgs:
+    when typeof(f) is StackTraceEntry:
+      add(s, f.frameMsg)
     else:
-      var oldLen = s.len
-      add(s, tempFrames[j].filename)
-      if tempFrames[j].line > 0:
-        add(s, '(')
-        add(s, $tempFrames[j].line)
-        add(s, ')')
-      for k in 1..max(1, 25-(s.len-oldLen)): add(s, ' ')
-      add(s, tempFrames[j].procname)
-    add(s, "\n")
-
-proc stackTraceAvailable*(): bool
+      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 NimStackTrace:
+    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")
+        add(s, noStacktraceAvailable)
       else:
         add(s, "Traceback (most recent call last)\n")
         auxWriteStackTrace(framePtr, s)
@@ -191,9 +342,20 @@ when hasSomeStackTrace:
       add(s, "Traceback from system (most recent call last)\n")
       auxWriteStackTraceWithBacktrace(s)
     else:
-      add(s, "No stack traceback available\n")
+      add(s, noStacktraceAvailable)
+
+  proc rawWriteStackTrace(s: var seq[StackTraceEntry]) =
+    when defined(nimStackTraceOverride):
+      auxWriteStackTraceWithOverride(s)
+    elif NimStackTrace:
+      auxWriteStackTrace(framePtr, s)
+    else:
+      s = @[]
+
   proc stackTraceAvailable(): bool =
-    when NimStackTrace:
+    when defined(nimStackTraceOverride):
+      result = true
+    elif NimStackTrace:
       if framePtr == nil:
         result = false
       else:
@@ -205,129 +367,273 @@ when hasSomeStackTrace:
 else:
   proc stackTraceAvailable*(): bool = result = false
 
-proc quitOrDebug() {.inline.} =
-  when not defined(endb):
-    quit(1)
+var onUnhandledException*: (proc (errorMsg: string) {.
+  nimcall, gcsafe.}) ## 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, gcsafe.} =
+  when hasSomeStackTrace:
+    var buf = newStringOfCap(2000)
+    if e.trace.len == 0:
+      rawWriteStackTrace(buf)
+    else:
+      var trace = $e.trace
+      add(buf, trace)
+      {.gcsafe.}:
+        `=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:
+      showErrorMessage2(buf)
+    {.gcsafe.}:
+      `=destroy`(buf)
   else:
-    endbStep() # call the debugger
+    # ugly, but avoids heap allocations :-)
+    template xadd(buf, s, slen) =
+      if L + slen < high(buf):
+        copyMem(addr(buf[L]), (when s is cstring: s else: 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)
+      {.gcsafe.}:
+        `=destroy`(trace)
+    add(buf, "Error: unhandled exception: ")
+    add(buf, e.msg)
+    add(buf, " [")
+    xadd(buf, e.name, e.name.len)
+    add(buf, "]\n")
+    if onUnhandledException != nil:
+      onUnhandledException($cast[cstring](buf.addr))
+    else:
+      showErrorMessage(cast[cstring](buf.addr), L)
+
+proc reportUnhandledError(e: ref Exception) {.nodestroy, gcsafe.} =
+  if unhandledExceptionHook != nil:
+    unhandledExceptionHook(e)
+  when hostOS != "any":
+    reportUnhandledErrorAux(e)
+
+when not gotoBasedExceptions:
+  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)
+        rawQuit(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
+      rawQuit(1)
+
+proc raiseExceptionAux(e: sink(ref Exception)) {.nodestroy.} =
+  when defined(nimPanics):
+    if e of Defect:
+      reportUnhandledError(e)
+      rawQuit(1)
 
-proc raiseExceptionAux(e: ref Exception) =
   if localRaiseHook != nil:
     if not localRaiseHook(e): return
   if globalRaiseHook != nil:
     if not globalRaiseHook(e): return
-  when defined(cpp):
-    if e[] of OutOfMemError:
-      showErrorMessage(e.name)
-      quitOrDebug()
+  when defined(cpp) and not defined(noCppExceptions) and not gotoBasedExceptions:
+    if e == currException:
+      {.emit: "throw;".}
     else:
       pushCurrentException(e)
-      {.emit: "throw NimException(`e`, `e`->name);".}
+      {.emit: "throw `e`;".}
+  elif quirkyExceptions or gotoBasedExceptions:
+    pushCurrentException(e)
+    when gotoBasedExceptions:
+      inc nimInErrorMode
   else:
     if excHandler != nil:
-      if not excHandler.hasRaiseAction or excHandler.raiseAction(e):
-        pushCurrentException(e)
-        c_longjmp(excHandler.context, 1)
-    elif e[] of OutOfMemError:
-      showErrorMessage(e.name)
-      quitOrDebug()
+      pushCurrentException(e)
+      c_longjmp(excHandler.context, 1)
     else:
-      when hasSomeStackTrace:
-        var buf = newStringOfCap(2000)
-        if isNil(e.trace): rawWriteStackTrace(buf)
-        else: add(buf, e.trace)
-        add(buf, "Error: unhandled exception: ")
-        if not isNil(e.msg): add(buf, e.msg)
-        add(buf, " [")
-        add(buf, $e.name)
-        add(buf, "]\n")
-        showErrorMessage(buf)
-      else:
-        # ugly, but avoids heap allocations :-)
-        template xadd(buf, s, slen: expr) =
-          if L + slen < high(buf):
-            copyMem(addr(buf[L]), cstring(s), slen)
-            inc L, slen
-        template add(buf, s: expr) =
-          xadd(buf, s, s.len)
-        var buf: array [0..2000, char]
-        var L = 0
-        add(buf, "Error: unhandled exception: ")
-        if not isNil(e.msg): add(buf, e.msg)
-        add(buf, " [")
-        xadd(buf, e.name, c_strlen(e.name))
-        add(buf, "]\n")
-        showErrorMessage(buf)
-      quitOrDebug()
-
-proc raiseException(e: ref Exception, ename: cstring) {.compilerRtl.} =
-  e.name = ename
+      reportUnhandledError(e)
+      rawQuit(1)
+
+proc raiseExceptionEx(e: sink(ref Exception), ename, procname, filename: cstring,
+                      line: int) {.compilerRtl, nodestroy.} =
+  if e.name.isNil: e.name = ename
   when hasSomeStackTrace:
-    e.trace = ""
-    rawWriteStackTrace(e.trace)
+    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")
+    sysFatal(ReraiseDefect, "no exception to reraise")
   else:
-    raiseExceptionAux(currException)
+    when gotoBasedExceptions:
+      inc nimInErrorMode
+    else:
+      raiseExceptionAux(currException)
+
+proc threadTrouble() =
+  # also forward declared, it is 'raises: []' hence the try-except.
+  try:
+    if currException != nil: reportUnhandledError(currException)
+  except:
+    discard
+  rawQuit 1
 
 proc writeStackTrace() =
   when hasSomeStackTrace:
     var s = ""
     rawWriteStackTrace(s)
-    showErrorMessage(s)
   else:
-    showErrorMessage("No stack traceback available\n")
+    let s = noStacktraceAvailable
+  cast[proc (s: string) {.noSideEffect, tags: [], nimcall, raises: [].}](showErrorMessage2)(s)
 
 proc getStackTrace(): string =
   when hasSomeStackTrace:
     result = ""
     rawWriteStackTrace(result)
   else:
-    result = "No stack traceback available\n"
+    result = noStacktraceAvailable
 
 proc getStackTrace(e: ref Exception): string =
-  if not isNil(e) and not isNil(e.trace):
-    result = e.trace
+  if not isNil(e):
+    result = $e.trace
   else:
     result = ""
 
-when defined(nimRequiresNimFrame):
-  proc stackOverflow() {.noinline.} =
-    writeStackTrace()
-    showErrorMessage("Stack overflow\n")
-    quitOrDebug()
-
-  proc nimFrame(s: PFrame) {.compilerRtl, inl, exportc: "nimFrame".} =
-    s.calldepth = if framePtr == nil: 0 else: framePtr.calldepth+1
-    s.prev = framePtr
-    framePtr = s
-    if s.calldepth == 2000: stackOverflow()
-else:
-  proc pushFrame(s: PFrame) {.compilerRtl, inl, exportc: "nimFrame".} =
-    # XXX only for backwards compatibility
-    s.prev = framePtr
-    framePtr = s
+proc getStackTraceEntries*(e: ref Exception): lent seq[StackTraceEntry] =
+  ## Returns the attached stack trace to the exception `e` as
+  ## a `seq`. This is not yet available for the JS backend.
+  e.trace
 
-when defined(endb):
-  var
-    dbgAborting: bool # whether the debugger wants to abort
+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()
+  let msg = "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"
+  showErrorMessage2(msg)
+  rawQuit(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 hostOS != "any" and not defined(noCppExceptions) and
+    not quirkyExceptions:
 
-when not defined(noSignalHandler):
-  proc signalHandler(sig: cint) {.exportc: "signalHandler", noconv.} =
-    template processSignal(s, action: expr) {.immediate,  dirty.} =
+  type
+    StdException {.importcpp: "std::exception", header: "<exception>".} = object
+
+  proc what(ex: StdException): cstring {.importcpp: "((char *)#.what())", nodecl.}
+
+  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"
+
+    rawQuit 1
+
+when not defined(noSignalHandler) and not defined(useNimRtl):
+  type Sighandler = proc (a: cint) {.noconv, benign.}
+    # xxx factor with ansi_c.CSighandlerT, posix.Sighandler
+
+  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:
-        when defined(endb):
-          if dbgAborting: return # the debugger wants to abort
         action("SIGABRT: Abnormal termination.\n")
       elif s == SIGFPE: action("SIGFPE: Arithmetic error.\n")
       elif s == SIGILL: action("SIGILL: Illegal operation.\n")
-      elif s == SIGBUS:
+      elif (when declared(SIGBUS): s == SIGBUS else: false):
         action("SIGBUS: Illegal storage access. (Attempt to read from nil?)\n")
       else:
         block platformSpecificSignal:
@@ -338,35 +644,62 @@ when not defined(noSignalHandler):
           action("unknown signal\n")
 
     # print stack trace and quit
+    when defined(memtracker):
+      logPendingOps()
     when hasSomeStackTrace:
-      GC_disable()
+      when not usesDestructors: GC_disable()
       var buf = newStringOfCap(2000)
       rawWriteStackTrace(buf)
-      processSignal(sig, buf.add) # nice hu? currying a la Nim :-)
-      showErrorMessage(buf)
-      GC_enable()
+      processSignal(sign, buf.add) # nice hu? currying a la Nim :-)
+      showErrorMessage2(buf)
+      when not usesDestructors: GC_enable()
     else:
       var msg: cstring
-      template asgn(y: expr) = msg = y
-      processSignal(sig, asgn)
-      showErrorMessage(msg)
-    when defined(endb): dbgAborting = true
-    quit(1) # always quit when SIGABRT
+      template asgn(y) =
+        msg = y
+      processSignal(sign, asgn)
+      # xxx use string for msg instead of cstring, and here use showErrorMessage2(msg)
+      # unless there's a good reason to use cstring in signal handler to avoid
+      # using gc?
+      showErrorMessage(msg, msg.len)
+
+    when defined(posix):
+      # reset the signal handler to OS default
+      c_signal(sign, SIG_DFL)
+
+      # re-raise the signal, which will arrive once this handler exit.
+      # this lets the OS perform actions like core dumping and will
+      # also return the correct exit code to the shell.
+      discard c_raise(sign)
+    else:
+      rawQuit(1)
+
+  var SIG_IGN {.importc: "SIG_IGN", header: "<signal.h>".}: Sighandler
 
   proc registerSignalHandler() =
+    # xxx `signal` is deprecated and has many caveats, we should use `sigaction` instead, e.g.
+    # https://stackoverflow.com/questions/231912/what-is-the-difference-between-sigaction-and-signal
     c_signal(SIGINT, signalHandler)
     c_signal(SIGSEGV, signalHandler)
     c_signal(SIGABRT, signalHandler)
     c_signal(SIGFPE, signalHandler)
     c_signal(SIGILL, signalHandler)
-    c_signal(SIGBUS, signalHandler)
+    when declared(SIGBUS):
+      c_signal(SIGBUS, signalHandler)
     when declared(SIGPIPE):
-      c_signal(SIGPIPE, signalHandler)
+      when defined(nimLegacySigpipeHandler):
+        c_signal(SIGPIPE, signalHandler)
+      else:
+        c_signal(SIGPIPE, SIG_IGN)
 
   registerSignalHandler() # call it in initialization section
 
-proc setControlCHook(hook: proc () {.noconv.} not nil) =
+proc setControlCHook(hook: proc () {.noconv.}) =
   # ugly cast, but should work on all architectures:
-  type SignalHandler = proc (sig: cint) {.noconv, benign.}
-  {.deprecated: [TSignalHandler: SignalHandler].}
-  c_signal(SIGINT, cast[SignalHandler](hook))
+  when declared(Sighandler):
+    c_signal(SIGINT, cast[Sighandler](hook))
+
+when not defined(noSignalHandler) and not defined(useNimRtl):
+  proc unsetControlCHook() =
+    # proc to unset a hook set by setControlCHook
+    c_signal(SIGINT, signalHandler)