1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
|
#
#
# 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.
var
errorMessageWriter*: (proc(msg: string) {.tags: [WriteIOEffect], benign.})
## 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) = write(stdmsg, msg)
else:
proc MessageBoxA(hWnd: cint, lpText, lpCaption: cstring, uType: int): int32 {.
header: "<windows.h>", nodecl.}
proc writeToStdErr(msg: cstring) =
discard MessageBoxA(0, msg, nil, 0)
proc showErrorMessage(data: cstring) =
if errorMessageWriter != nil:
errorMessageWriter($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.}
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
proc popFrame {.compilerRtl, inl.} =
framePtr = framePtr.prev
proc setFrame(s: PFrame) {.compilerRtl, inl.} =
framePtr = s
proc pushSafePoint(s: PSafePoint) {.compilerRtl, inl.} =
s.hasRaiseAction = false
s.prev = excHandler
excHandler = s
proc popSafePoint {.compilerRtl, inl.} =
excHandler = excHandler.prev
proc pushCurrentException(e: ref Exception) {.compilerRtl, inl.} =
e.parent = currException
currException = e
proc popCurrentException {.compilerRtl, inl.} =
currException = nil # currException.parent
# some platforms have native support for stack traces:
const
nativeStackTraceSupported* = (defined(macosx) or defined(linux)) and
not NimStackTrace
hasSomeStackTrace = NimStackTrace 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 [0..127, 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
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 not hasThreadSupport:
var
tempFrames: array [0..127, 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
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) ...")
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
when hasSomeStackTrace:
proc rawWriteStackTrace(s: var string) =
when 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 stackTraceAvailable(): bool =
when 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
proc quitOrDebug() {.inline.} =
when not defined(endb):
quit(1)
else:
endbStep() # call the debugger
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()
else:
pushCurrentException(e)
{.emit: "throw NimException(`e`, `e`->name);".}
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()
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
when hasSomeStackTrace:
e.trace = ""
rawWriteStackTrace(e.trace)
raiseExceptionAux(e)
proc reraiseException() {.compilerRtl.} =
if currException == nil:
sysFatal(ReraiseError, "no exception to reraise")
else:
raiseExceptionAux(currException)
proc writeStackTrace() =
when hasSomeStackTrace:
var s = ""
rawWriteStackTrace(s)
showErrorMessage(s)
else:
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) and not isNil(e.trace):
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
when defined(endb):
var
dbgAborting: bool # whether the debugger wants to abort
when not defined(noSignalHandler):
proc signalHandler(sig: cint) {.exportc: "signalHandler", noconv.} =
template processSignal(s, action: expr) {.immediate, 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:
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 hasSomeStackTrace:
GC_disable()
var buf = newStringOfCap(2000)
rawWriteStackTrace(buf)
processSignal(sig, buf.add) # nice hu? currying a la Nim :-)
showErrorMessage(buf)
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
proc registerSignalHandler() =
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(SIGPIPE):
c_signal(SIGPIPE, signalHandler)
registerSignalHandler() # call it in initialization section
proc setControlCHook(hook: proc () {.noconv.} not nil) =
# ugly cast, but should work on all architectures:
type SignalHandler = proc (sig: cint) {.noconv, benign.}
{.deprecated: [TSignalHandler: SignalHandler].}
c_signal(SIGINT, cast[SignalHandler](hook))
|