Giter VIP home page Giter VIP logo

nim-libbacktrace's Introduction

All the backtrace, none of the overhead

Github action License: Apache License: MIT Stability: experimental

Nim's default stack tracing functionality comes with significant overhead, by adding nimln_(), nimfr_() calls all over the place. The problem is being discussed upstream in this GitHub issue.

In practice, you can get as much as 66% improved performance by disabling the default stack tracing: status-im/nimbus-eth2#3466

That popFrame() at the end of each C function is particularly problematic, since it prevents the C compiler from doing tail-call optimisations.

This is a lightweight alternative based on libbacktrace, meant to offer the same stack traces without the runtime overhead.

C++ function name demangling is supported using "__cxa_demangle()".

Building & Testing

This project uses Git submodules, so get it with:

git clone https://github.com/status-im/nim-libbacktrace.git
cd nim-libbacktrace
git submodule update --init

You build the library (or libraries, on macOS) with make. You test it with make test.

Nimble is grudgingly supported, so nimble install works. (No, we will not let a silly package manager dictate our project's structure. People have the power!)

Supported platforms

Tested with GCC and LLVM on Linux, macOS and 64-bit Windows (with Mingw-w64 and the MSYS that comes with "Git for Windows").

Usage

bttest.nim:

import libbacktrace

# presumably in some procedure:
echo getBacktrace()

# Should be the same output as writeStackTrace() - minus the header.

We need debugging symbols in the binary and we can do without Nim's bloated and slow stack trace implementation:

# `-f` needed if you've changed nim-libbacktrace
nim c -r --debugger:native --stacktrace:off bttest.nim

By default, the Nim compiler passes "-g3" to the C compiler, with "--debugger:native", which almost doubles the resulting binary's size (only on disk, not in memory). If we don't need to use GDB on that binary, we can get away with significantly fewer debugging symbols by switching to "-g1":

# for the C backend
nim c -d:release --debugger:native --gcc.options.debug:'-g1' somefile.nim

# for the C++ backend
nim cpp -d:release --debugger:native --gcc.cpp.options.debug:'-g1' somefile.nim

# Clang needs a different argument
nim c -d:release --cc:clang --debugger:native --clang.options.debug:'-gline-tables-only' somefile.nim

When the C compiler inlines some functions, or does tail-call optimisation - usually with -d:release or -d:danger - your stack trace might be incomplete.

If that's a problem, you can use --passC:"-fno-inline -fno-optimize-sibling-calls".

Two-step backtraces

When you store backtraces in re-raised exceptions, you won't need to print them most of the time, so it makes sense to delay the expensive part of debugging info collection until it's actually needed:

let maxLength: cint = 128

# Unwind the stack and get a seq of program counters - the fast step:
let programCounters = getProgramCounters(maxLength)

# Later on, when you need to print these backtraces, get the debugging
# info - the relatively slow step:
let entries = getDebuggingInfo(programCounters, maxLength)

If you have multiple backtraces - and yo do with a re-raised exception - you should pass subsets of program counters representing complete stack traces to getDebuggingInfo(), because there's some logic inside it that keeps track of certain inlined functions in order to change the output

You may get more StackTraceEntry objects than the program counters you passed to getDebuggingInfo(), when you have inlined functions and the debugging format knows about them (DWARF does).

Debugging

export NIM_LIBBACKTRACE_DEBUG=1 to see the trace lines hidden by default.

Nim compiler support

Nim 1.0.6 supports replacing the default stack tracing mechanism with an external one.

This means you no longer have to call getBacktrace() yourself, if you compile your program like this:

nim c -r --debugger:native --stacktrace:off -d:nimStackTraceOverride --import:libbacktrace foo.nim

You can even use libbacktrace in the Nim compiler itself, by building it with:

./koch boot -d:release --debugger:native -d:nimStackTraceOverride --import:libbacktrace

(-d:release implies --stacktrace:off)

Dependencies

You need Make, CMake and, of course, Nim up and running.

The other dependencies are bundled, for your convenience. We use a libbacktrace fork with macOS support and LLVM's libunwind variant that's needed on macOS and Windows.

If you know better and want to use your system's libbacktrace package instead of the bundled one, you can, with make USE_SYSTEM_LIBS=1 and by passing -d:libbacktraceUseSystemLibs to the Nim compiler.

How does libbacktrace work on systems without libunwind installed, I hear you asking? It uses GCC's basic unwind support in libgcc_s.so.1 - that runtime's so good that even Clang links it by default ;-)

If you don't want to build the C++ wrapper, for some reason, pass BUILD_CXX_LIB=0 to Make.

To get the running binary's path in a cross-platform way, we rely on whereami.

License

Licensed and distributed under either of

or

at your option. These files may not be copied, modified, or distributed except according to those terms.

nim-libbacktrace's People

Contributors

etan-status avatar jangko avatar stefantalpalaru avatar timotheecour avatar yyoncho avatar zah avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

nim-libbacktrace's Issues

no stacktrace shown when signalHandler called (eg `SIGSEGV`)

I'm trying to get a stacktrace for nim-lang/Nim#17157:

  • it works with nim's stacktraces
  • it doesn't work with d:nimStackTraceOverride:
# regular nim stacktrace:
nim c -o:bin/nim1 --stacktrace:on compiler/nim.nim

# nimStackTraceOverride:
nim c -lib:lib -o:bin/nim2 --stacktrace:off -d:nimStackTraceOverride --import:libbacktrace -g --passc:"-fno-omit-frame-pointer -fno-optimize-sibling-calls" compiler/nim.nim

bin/nim1 c -d:case2 main # shows a complete backtrace
bin/nim2 c -d:case2 main # shows a very limited backtrace:
Traceback (most recent call last, using override)
lib/system/excpt.nim(630) signalHandler
SIGSEGV: Illegal storage access. (Attempt to read from nil?)
[1]    50275 segmentation fault  bin/nim2 c --lib:lib main.nim

details

main.nim: example from nim-lang/Nim#17157:

template something(op: proc (v: untyped): void): void = discard

nim: 1.5.1 95697d00fa7e6f642c1e99b2baa4d76e468d1c77
nim-libbacktrace: 40be351

note 1

adding --passc:"-fno-omit-frame-pointer -fno-optimize-sibling-calls" didn't help. Note that this flag is useful in combination with optimized builds to get more complete stacktraces, however it didn't help here. It did help with the example in #5, a full stacktrace was obtained thanks to it even with -d:release or -d:danger. IMO we should mention this flag in docs for -d:nimStackTraceOverride, at least if this issue can get fixed

note 2

with -d:noSignalHandler i'll just get:

[1]    56734 segmentation fault  bin/nim.r5 c -d:case2 --lib:lib main.nim

note 3

if i add doAssert false right before the place that causes SIGSEGV in compiler, it works:

proc isMagic(sym: PSym): bool =
  if sym.ast == nil:
    doAssert false # added this
  let nPragmas = sym.ast[pragmasPos]
compiler/nim.nim(125)    nim
compiler/nim.nim(84)     handleCmdLine
compiler/main.nim(242)   mainCommand
compiler/main.nim        compileToBackend
compiler/main.nim(90)    commandCompileToC
compiler/modules.nim     compileProject
compiler/modules.nim(97) compileModule
compiler/passes.nim(180) processModule
compiler/passes.nim(73)  processTopLevelStmt
compiler/sem.nim(607)    myProcess
compiler/sem.nim(575)    semStmtAndGenerateGenerics
compiler/semstmts.nim(2306) semStmt
compiler/semexprs.nim(1042) semExprNoType
compiler/semexprs.nim(2892) semExpr
compiler/semstmts.nim(2248) semStmtList
compiler/semexprs.nim    semExpr
compiler/semexprs.nim(2340) semWhen
compiler/semexprs.nim(2892) semExpr
compiler/semstmts.nim(2248) semStmtList
compiler/semexprs.nim(2915) semExpr
compiler/semtempl.nim(630) semTemplateDef
compiler/semstmts.nim(1421) semParamList
compiler/semtypes.nim(1233) semProcTypeNode
compiler/semtypes.nim(1183) semParamType
compiler/semtypes.nim(1980) semTypeNode
compiler/semtypes.nim(1662) semProcTypeWithScope
compiler/semtypes.nim(1236) semProcTypeNode
compiler/semtypes.nim(1198) isMagic

but then interestingly, it doesn't show:

Traceback (most recent call last, using override)

on top, which also seems like a bug

without --passc:"-fno-omit-frame-pointer -fno-optimize-sibling-calls" it shows 25 frames instead of 29, which makes sense.

So it looks like SIGSEGV (which triggers signal handler) causes the stacktraces to disappear

note 4

I'm not getting absolute paths even if I specify --listfullpaths or --excessivestacktrace

note 5: reduced example

when true:
  proc fun() =
    var x: ref int
    echo x[]
  fun()

nim r --stacktrace:on t12012.nim

Traceback (most recent call last)
/Users/timothee/git_clone/nim/timn/tests/nim/all/t12012.nim(45) t12012
/Users/timothee/git_clone/nim/timn/tests/nim/all/t12012.nim(44) fun
SIGSEGV: Illegal storage access. (Attempt to read from nil?)

nim r --stacktrace:off -d:nimStackTraceOverride --import:libbacktrace -g t12012.nim

Traceback (most recent call last, using override)
/Users/timothee/git_clone/nim/Nim_devel/lib/system/excpt.nim(630) signalHandler
SIGSEGV: Illegal storage access. (Attempt to read from nil?)

note 6: lldb works

lldb t12012 (compiled as in note 5) does work and shows the relevant parts of the stacktrace

so this seems like a bug in nim-libbacktrace

(lldb) r
Process 91149 launched: '/Users/timothee/git_clone/nim/timn/build/nimcache/t12012' (x86_64)
Process 91149 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
    frame #0: 0x000000010000ee73 start-addr:0x000000010000ee73  t12012 fun__1TeY7TkO6UnbBIEVdjnblg + 51 at  /Users/timothee/git_clone/nim/timn/tests/nim/all/t12012.nim:44:24
   41   when defined case5:
   42     proc fun() =
   43       var x: ref int
-> 44       echo x[]
   45     fun()
Target 0: (t12012) stopped.
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
  * frame #0: 0x000000010000ee73 start-addr:0x000000010000ee73  t12012 fun__1TeY7TkO6UnbBIEVdjnblg + 51 at  /Users/timothee/git_clone/nim/timn/tests/nim/all/t12012.nim:44:24
    frame #1: 0x000000010000ef99 start-addr:0x000000010000ef94  t12012 NimMainModule + 9 at  /Users/timothee/git_clone/nim/timn/tests/nim/all/t12012.nim:45:2
    frame #2: 0x000000010000ef89 start-addr:0x000000010000ef84  t12012 NimMainInner + 9 at  /Users/timothee/git_clone/nim/Nim_devel/lib/system.nim:2222:2
    frame #3: 0x000000010000efca start-addr:0x000000010000efc8  t12012 NimMain + 42 at  /Users/timothee/git_clone/nim/Nim_devel/lib/system.nim:2230:2
    frame #4: 0x000000010000f00e start-addr:0x000000010000f009  t12012 main(argc=1, args=0x00007ffeefbfae88, env=0x00007ffeefbfae98) + 62 at  /Users/timothee/git_clone/nim/Nim_devel/lib/system.nim:2237:2
  frame #5: 0x00007fff6a526cc9 libdyld.dylib`start + 1
  frame #6: 0x00007fff6a526cc9 libdyld.dylib`start + 1
(lldb)

note 6

after instrumenting and playing with nim-libbacktrace code, i found NIM_LIBBACKTRACE_DEBUG, it turns out with this flag it works:

NIM_LIBBACKTRACE_DEBUG=1 nim r -b:cpp --stacktrace:off -d:nimStackTraceOverride --import:libbacktrace -g -d:case5 -f $timn_D/tests/nim/all/t12012.nim

Traceback (most recent call last, using override)
/Users/timothee/git_clone/nim/Nim_devel/lib/system.nim(2237) main
/Users/timothee/git_clone/nim/Nim_devel/lib/system.nim(2230) NimMain
/Users/timothee/git_clone/nim/Nim_devel/lib/system.nim(2222) NimMainInner
/Users/timothee/git_clone/nim/timn/tests/nim/all/t12012.nim(45) t12012
/Users/timothee/git_clone/nim/timn/tests/nim/all/t12012.nim(44) fun
/Users/timothee/git_clone/nim/Nim_devel/lib/system/excpt.nim(630) signalHandler
/Users/timothee/git_clone/nim/Nim_devel/lib/system/excpt.nim(314) rawWriteStackTrace
/Users/timothee/git_clone/nim/Nim_devel/lib/system/stacktraces.nim(59) auxWriteStackTraceWithOverride
/Users/timothee/git_clone/nim/nim-libbacktrace/libbacktrace.nim(47) getBacktrace
./libbacktrace_wrapper.c(352) get_backtrace_max_length_c
./libbacktrace_wrapper.c(286) get_program_counters_c
SIGSEGV: Illegal storage access. (Attempt to read from nil?)
Error: execution of an external program failed: '/Users/timothee/git_clone/nim/timn/build/nimcache/t12012 '

=> the relevant part of stacktrace is now shown:

/Users/timothee/git_clone/nim/timn/tests/nim/all/t12012.nim(45) t12012
/Users/timothee/git_clone/nim/timn/tests/nim/all/t12012.nim(44) fun

so this is definitely a bug in nim-libbacktrace

note 7

going back to original example, NIM_LIBBACKTRACE_DEBUG=1 makes it work.
with nim compiled with: --passc:"-fno-omit-frame-pointer -fno-optimize-sibling-calls" -d:danger

i get:

Traceback (most recent call last, using override)
lib/system.nim(2311) main
lib/system.nim(2304) NimMain
lib/system.nim(2296) NimMainInner
compiler/nim.nim(125) nim
compiler/nim.nim(84) handleCmdLine
compiler/main.nim(242) mainCommand
compiler/main.nim(0) compileToBackend
compiler/main.nim(90) commandCompileToC
compiler/modules.nim(0) compileProject
compiler/modules.nim(97) compileModule
compiler/passes.nim(180) processModule
compiler/passes.nim(73) processTopLevelStmt
compiler/sem.nim(607) myProcess
compiler/sem.nim(575) semStmtAndGenerateGenerics
compiler/semstmts.nim(2306) semStmt
compiler/semexprs.nim(1042) semExprNoType
compiler/semexprs.nim(2892) semExpr
compiler/semstmts.nim(2248) semStmtList
compiler/semexprs.nim(0) semExpr
compiler/semexprs.nim(2340) semWhen
compiler/semexprs.nim(2892) semExpr
compiler/semstmts.nim(2248) semStmtList
compiler/semexprs.nim(2915) semExpr
compiler/semtempl.nim(630) semTemplateDef
compiler/semstmts.nim(1421) semParamList
compiler/semtypes.nim(1233) semProcTypeNode
compiler/semtypes.nim(1183) semParamType
compiler/semtypes.nim(1980) semTypeNode
compiler/semtypes.nim(1662) semProcTypeWithScope
compiler/semtypes.nim(1236) semProcTypeNode
compiler/semtypes.nim(1200) isMagic
lib/system/excpt.nim(630) signalHandler
lib/system/excpt.nim(314) rawWriteStackTrace
lib/system/stacktraces.nim(59) auxWriteStackTraceWithOverride
/Users/timothee/git_clone/nim/nim-libbacktrace/libbacktrace.nim(47) getBacktrace
/Users/timothee/git_clone/nim/nim-libbacktrace/libbacktrace_wrapper.c(352) get_backtrace_max_length_c
/Users/timothee/git_clone/nim/nim-libbacktrace/libbacktrace_wrapper.c(286) get_program_counters_c
SIGSEGV: Illegal storage access. (Attempt to read from nil?)
  • without -d:danger i get 38 frames
  • with -d:danger i get 32 frames (32 < 38 as expected)
  • with --passc:"-fno-omit-frame-pointer -fno-optimize-sibling-calls" -d:danger i get 38 frames; notice the frames with line = 0; I'm curious whether we can report correct line numbers or whether that's the best we can do here

note that --passc:"-fno-omit-frame-pointer -fno-optimize-sibling-calls" -d:danger gives best of both worlds:

  • similar speed as nim built with -d:danger (0.6s runtime up to the crash)
  • almost same stacktrace as the one built with --stacktrace:on

note 8

with:
NIM_LIBBACKTRACE_DEBUG=1 lldb -- bin/nim.r10 c -d:case2 --lib:lib $timn_D/tests/nim/all/t12012.nim
I also get some line numbers being 0, so maybe there's the line info is unavailable for those frames:

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x18)
  * frame #0: 0x000000010017d9b1 start-addr:0x000000010017d9b1  nim.r10 semProcTypeNode__MdjmOB9curTzVOmkS3WHYZg [inlined] isMagic__jKjjve5xVHAVUj9b7USTS9bA(sym=<unavailable>) + 4 at  /Users/timothee/git_clone/nim/Nim_prs/compiler/semtypes.nim:1200:35
    frame #1: 0x000000010017d9ad start-addr:0x000000010017d9ad  nim.r10 semProcTypeNode__MdjmOB9curTzVOmkS3WHYZg(c=0x0000000105dd3050, n=0x0000000105d00f90, genericParams=0x0000000000000000, prev=<unavailable>, kind=<unavailable>, isType=true) + 605
    frame #2: 0x000000010019a054 start-addr:0x000000010019a039  nim.r10 semProcTypeWithScope__VVQPTRRjDGNV17td9cICm7g(c=0x0000000105dd3050, n=0x0000000105cfb8d0, prev=0x0000000000000000, kind='\f') + 180 at  /Users/timothee/git_clone/nim/Nim_prs/compiler/semtypes.nim:1662:11
    frame #3: 0x0000000100170b7d start-addr:0x0000000100170b6a  nim.r10 semTypeNode__tYJL0pqPjh78ZSGOkWDaSA(c=0x0000000105dd3050, n=0x0000000105cfb8d0, prev=0x0000000000000000) + 1181 at  /Users/timothee/git_clone/nim/Nim_prs/compiler/semtypes.nim:1980:13
    frame #4: 0x0000000100179283 start-addr:0x0000000100179276  nim.r10 semParamType__al5PH9cqwbxAfK9bAuQImZdw(c=0x0000000105dd3050, n=0x0000000105cfb8d0, constraint=0x00007ffeefbfa000) + 179 at  /Users/timothee/git_clone/nim/Nim_prs/compiler/semtypes.nim:1183:12
    frame #5: 0x000000010017d98f start-addr:0x000000010017d980  nim.r10 semProcTypeNode__MdjmOB9curTzVOmkS3WHYZg(c=0x0000000105dd3050, n=0x0000000105d009d0, genericParams=0x0000000105bc7110, prev=<unavailable>, kind=<unavailable>, isType=false) + 575 at  /Users/timothee/git_clone/nim/Nim_prs/compiler/semtypes.nim:1233:13
    frame #6: 0x00000001001a23eb start-addr:0x00000001001a23da  nim.r10 semTemplateDef__7YxUILpPxooC5qqa89a54dw_37 [inlined] semParamList__Eyq3IMn6PJevR76ZZ2uLQQ(c=0x0000000105dd3050, n=<unavailable>, genericParams=0x0000000105bc7110, s=0x0000000105d2aad0) + 22 at  /Users/timothee/git_clone/nim/Nim_prs/compiler/semstmts.nim:1421:32
    frame #7: 0x00000001001a23d5 start-addr:0x00000001001a23d5  nim.r10 semTemplateDef__7YxUILpPxooC5qqa89a54dw_37(c=0x0000000105dd3050, n=0x0000000105d00c90) + 629
    frame #8: 0x000000010016f354 start-addr:0x000000010016f349  nim.r10 semExpr__vJZwz9bfROKrfrtM50y74CQ_3(c=0x0000000105dd3050, n=0x0000000105d00c90, flags=0) + 3604 at  /Users/timothee/git_clone/nim/Nim_prs/compiler/semexprs.nim:2915:12
    frame #9: 0x00000001001a8492 start-addr:0x00000001001a8487  nim.r10 semStmtList__vJZwz9bfROKrfrtM50y74CQ_30(c=0x0000000105dd3050, n=0x0000000105d00f10, flags=0) + 162 at  /Users/timothee/git_clone/nim/Nim_prs/compiler/semstmts.nim:2248:13
    frame #10: 0x000000010016eb46 start-addr:0x000000010016eb39  nim.r10 semExpr__vJZwz9bfROKrfrtM50y74CQ_3(c=0x0000000105dd3050, n=0x0000000105d00f10, flags=0) + 1542 at  /Users/timothee/git_clone/nim/Nim_prs/compiler/semexprs.nim:2892:12
    frame #11: 0x0000000100196e95 start-addr:0x0000000100196e88  nim.r10 semWhen__5C4HPXQuhxlMYIzPbe36lg(c=0x0000000105dd3050, n=<unavailable>, semCheck=true) + 1077 at  /Users/timothee/git_clone/nim/Nim_prs/compiler/semexprs.nim:2340:18
    frame #12: 0x000000010016fa42 start-addr:0x000000010016fa26  nim.r10 semExpr__vJZwz9bfROKrfrtM50y74CQ_3(c=0x0000000105dd3050, n=0x0000000105d00cd0, flags=64) + 5378
    frame #13: 0x00000001001a8492 start-addr:0x00000001001a8487  nim.r10 semStmtList__vJZwz9bfROKrfrtM50y74CQ_30(c=0x0000000105dd3050, n=0x0000000105d00e90, flags=64) + 162 at  /Users/timothee/git_clone/nim/Nim_prs/compiler/semstmts.nim:2248:13
    frame #14: 0x000000010016eb46 start-addr:0x000000010016eb39  nim.r10 semExpr__vJZwz9bfROKrfrtM50y74CQ_3(c=0x0000000105dd3050, n=0x0000000105d00e90, flags=64) + 1542 at  /Users/timothee/git_clone/nim/Nim_prs/compiler/semexprs.nim:2892:12
    frame #15: 0x00000001001b68d8 start-addr:0x00000001001b68c8  nim.r10 semStmtAndGenerateGenerics__7YxUILpPxooC5qqa89a54dw_65 [inlined] semExprNoType__7YxUILpPxooC5qqa89a54dw_2(c=0x0000000105dd3050, n=0x0000000105d00e90) + 71 at  /Users/timothee/git_clone/nim/Nim_prs/compiler/semexprs.nim:1042:11
    frame #16: 0x00000001001b6891 start-addr:0x00000001001b6891  nim.r10 semStmtAndGenerateGenerics__7YxUILpPxooC5qqa89a54dw_65 [inlined] semStmt__vJZwz9bfROKrfrtM50y74CQ_4(c=0x0000000105dd3050, n=0x0000000105d00e90, flags=0)
    frame #17: 0x00000001001b6891 start-addr:0x00000001001b6891  nim.r10 semStmtAndGenerateGenerics__7YxUILpPxooC5qqa89a54dw_65(c=0x0000000105dd3050, n=<unavailable>) + 65
    frame #18: 0x00000001001b6c15 start-addr:0x00000001001b6c0a  nim.r10 myProcess__QAJdPOwMq9buTNGNGQO3ImA(context=0x0000000105dd3050, n=0x0000000105d00e90) + 69 at  /Users/timothee/git_clone/nim/Nim_prs/compiler/sem.nim:607:12
    frame #19: 0x00000001000fe62a start-addr:0x00000001000fe625  nim.r10 processModule__5pRk9bz8P1JWU809a9b9cPz0Dg + 56 at  /Users/timothee/git_clone/nim/Nim_prs/compiler/passes.nim:73:10
    frame #20: 0x00000001000fe5f2 start-addr:0x00000001000fe5f2  nim.r10 processModule__5pRk9bz8P1JWU809a9b9cPz0Dg(graph=0x000000010067e050, module=0x0000000105c5add0, idgen=<unavailable>, stream=<unavailable>) + 1362
    frame #21: 0x0000000100232e4d start-addr:0x0000000100232e3c  nim.r10 compileModule__JSGrRuKEntiB6El8fj8npQ(graph=0x000000010067e050, fileIdx=<unavailable>, flags=<unavailable>) + 701 at  /Users/timothee/git_clone/nim/Nim_prs/compiler/modules.nim:97:33
    frame #22: 0x0000000100233340 start-addr:0x000000010023333b  nim.r10 compileProject__KlDcF6rnOBDQLo439cvjPjg(graph=0x000000010067e050, projectFileIdx=<unavailable>) + 544
    frame #23: 0x00000001002847d0 start-addr:0x00000001002847c3  nim.r10 commandCompileToC__ikP0vuP6oxqlYdG7q9cY7sA_2(graph=0x000000010067e050) + 192 at  /Users/timothee/git_clone/nim/Nim_prs/compiler/main.nim:90:2
    frame #24: 0x0000000100284b3c start-addr:0x0000000100284b23  nim.r10 compileToBackend__vfArexHrJ9cN5iCnRabJjQg(ClE_0=0x0000000104d52350) + 76 at  /Users/timothee/git_clone/nim/Nim_prs/compiler/main.nim:0:2
    frame #25: 0x00000001002855a7 start-addr:0x000000010028559f  nim.r10 mainCommand__ikP0vuP6oxqlYdG7q9cY7sA(graph=<unavailable>) + 727 at  /Users/timothee/git_clone/nim/Nim_prs/compiler/main.nim:242:167
    frame #26: 0x0000000100287d19 start-addr:0x0000000100287d11  nim.r10 handleCmdLine__I9aitIr13Z1B6wHv5vzMf2w(cache=0x0000000100643030, conf=0x0000000100639050) + 217 at  /Users/timothee/git_clone/nim/Nim_prs/compiler/nim.nim:84:2
    frame #27: 0x00000001002883b6 start-addr:0x00000001002883ae  nim.r10 NimMainModule + 118 at  /Users/timothee/git_clone/nim/Nim_prs/compiler/nim.nim:125:2
    frame #28: 0x0000000100288339 start-addr:0x0000000100288334  nim.r10 NimMainInner + 9 at  /Users/timothee/git_clone/nim/Nim_prs/lib/system.nim:2296:2
    frame #29: 0x000000010028844b start-addr:0x0000000100288449  nim.r10 main [inlined] NimMain + 59 at  /Users/timothee/git_clone/nim/Nim_prs/lib/system.nim:2304:2
    frame #30: 0x000000010028842c start-addr:0x000000010028842c  nim.r10 main(argc=<unavailable>, args=<unavailable>, env=<unavailable>) + 28
  frame #31: 0x00007fff6a526cc9 libdyld.dylib`start + 1

note 9

what about column numbers? it should be available as shown in lldb stacktrace, so we should expose it instead of just showing line numbers

`--import:libbacktrace`, `-g` and `--stacktrace:off` shouldn't be needed

nimStackTraceOverride should be usable with a single switch.
this works:

nim r --import:libbacktrace -d:nimStackTraceOverride -g main.nim

but is not ideal because libbacktrace gets imported everywhere

adding explicit import libbacktrace is also not ideal because it requires changing source file

proposal

  • make -d:nimStackTraceOverride cause an import libbacktrace where it's neeed (probably a single file in nim repo)

if this needs compiler support, it's still worth it.

  • furthermore, -g should be implied by -d:nimStackTraceOverride
  • ditto with --stacktrace:off

we can also add: --stacktrace:libbacktrace nim flag to make it more official

(this may require fixing in nim repo instead of here, but let's keep this open until this is fixed, I can also look into it)

note

deferred stacktraces (`getStackTrace(e: ref Exception)`) don't work (works with standard nim stacktraces)

standard nim stacktraces allow getting stacktraces stored in an Exception (eg for use in a catch statement); but libbacktrace ones don't work in this case (they only work in non-deferred mode, eg when crashing with doAssert false), so it's not a suitable replacement for standard stacktraces at the moment.

I had originally opened #4 as I assumed the code did "eager" symbolication which is not efficient but in fact I now realize it doesn't do any symbolication at all for caught exceptions; feel free to close either this issue or #4 if you consider them duplicates (although #4 deals with performance and this one deals with correctness).

As I mentioned in #4, one way to fix this would be using something like what D does (https://github.com/dlang/druntime/blob/master/src/rt/backtrace/dwarf.d) which collects backtrace (cheap) then symbolicates if needed later. However it's not yet clear to me whether libbacktrace supports deferred symbolication (see ianlancetaylor/libbacktrace#35)

example

# standard nim stacktraces: works (shows a stacktrace)
nim c -r --stacktrace:on -d:case2c -g $timn_D/tests/nim/all/t10308.nim

# libbacktrace stacktraces: doesn't work (shows empty stacktrace)
nim c -r --stacktrace:off -d:nimStackTraceOverride --import:libbacktrace -g main.nim
# main.nim
proc fun() =
  # doAssert false # this works, correctly showing a stacktrace
  raise newException(CatchableError, "foo")
try: fun()
except CatchableError as e:
  echo "caught:\n" & e.getStacktrace() # BUG: no stacktrace shown
echo "after"

this outputs:

caught:

after

instead of showing a stacktrace, like in standard nim stacktraces

links

D20200308T163130

[performance] deferred stacktrace symbolication

I can provide a benchmark showing this could make a significant difference if needed to justify this feature.
The idea is to collect backtraces as efficiently as possible, and symbolicate them at a later time, via a proc:

proc symbolicate(result: var string, addresses: ptr ByteAddress, len: int)

where addresses are provided for eg via backtrace (see http://man7.org/linux/man-pages/man3/backtrace.3.html)
and the symbolication is provided via libbacktrace; I don't know whether libbacktrace supports symbolication "after the fact" given an array of addresses, hopfully it should (just needs to read and symbolicate DWARF info attached to input addresses, in theory)

use cases

  • nimprof, which collects at regular intervals a full backtrace, and then aggregates these to produce profiling data (at granularity of full backtraces, not just individual function calls)
  • (I need to verify this one) IIUC right now you pay a penalty with try/catch by symbolicating the trace even if user doesn't require symbolication (eg via getStacktrace(e)); this feature would make symbolication lazy, ie as needed; resulting in faster exception handling (in the common case where a caught exception isn't symbolicated)

note

(somewhat related)
nim's own standard backtrace approach could be made much more efficient it it didn't symbolicate at every frame (by having a filename, procname line info etc at each TFrame); instead all it'd need would be to have at each frame a unique int index pointing to a debug info that could be stored somewhere else.

note

that debug info could even in a separate file so we can "strip" debug info from a binary while retaining ability to generate symbolicated stacktraces in a separate process; this feature is useful (and in heavy use) eg when shipping stripped binaries to your clients, collecting stacktraces as opaque addresses, and symbolicating on a remote server.

links

D's approach: uses something a bit similar, on thrown exception, we just collect addresses (eg via backtrace), and at a later time, when needed, we symbolicate, see https://github.com/dlang/druntime/blob/master/src/rt/backtrace/dwarf.d

[performance] `proc getBacktrace*(): string {.noinline.}` should reuse input buffer

getBacktrace can be performance critical in several cases:

  • in exception heavy code, with lots of thrown/caught exceptions
  • when dealing with deeply nested code (eg nim compiler itself can generate deep nesting)
  • when used in combination with nimprof which generates a lot of stacktraces for profiling purposes

proposal

  • replace proc getBacktrace*(): string {.noinline.} by proc getBacktrace*(result: var string) {.noinline.}
    and make sure the buffer passed in getBacktrace is allocated only once all the way down to system/excpt.nim (the buffer would be a {.threadvar.} in system/excpt.nim)

result = newString(btLen)
would become a string append instead, resulting in O(1) amortized cost instead of keep reallocating on each call to getBacktrace

  • (a bit more complex) furthermore, get_backtrace_c could also reuse the same buffer to avoid allocations; this is doable but takes a bit more care (all that's needed is to expose the nim setLen proc to the C world via exportc, so that the C code can resize the buffer as needed

stacktrace doesn't honor `--excessivestacktrace`

as shown in #9, see note 4
I'm expecting full paths to be shown, just as in nim's stacktrace:on, when specifying --excessivestacktrace; in particular this is useful if some files our out of cwd, but not just that: in some cases programms can change the cwd and the generate a stacktrace, so absolute paths make this clear.

Happy to provide more details here in case this isn't clear enough.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.