Hi all, here's an update on my progress with backtraces.
I started off by looking at the various options we have:
-
backtrace()
for unwinding andbacktrace_symbols()
for symbolication. This is the approach currently used in GitHub - swift-server/swift-backtrace: 💥 Backtraces for Swift on Linux and Windows and while it does successfully unwind it does not fully symbolicate, becausebacktrace_symbols()
does not read DWARF debug info.addr2line
can be used post-mortem to symbolicate. -
Then I thought "Well if
addr2line
is symbolicating, can't we just do whatever it is doing internally?" Yes we can, and https://oroboro.com/printing-stack-traces-file-line/ explains nicely how to do it. Basically you can uselibbfd
which is part of GNU binutils. The problem with this is thatlibbfd
is GPL licensed, which makes it unusable for SSWG purposes. -
Next I looked at
libunwind
. This is liberally licensed and can symbolicate from DWARF info so could be a good option. GitHub - norio-nomura/SwiftBacktrace: Stack traces for Swift on Mac and Linux using `libunwind`. has proven that it can work. The only downside is that it requires installing thelibunwind8
package. It would be nice if we had a solution that worked out-of-the-box. -
Next I looked at
libdw
. This is part of elfutils and again looks like it ticks the boxes. It is used successfully by the Haskell runtime for stacktraces and symbolication, and is also an unwind/symbolicate backend for Linuxperf
. It doesn't look easy to work with though from a quick look at the API, and the licensing is LGPLv3+ which may be problematic. It may well be worth investing more time looking at it though. -
Next I looked at
eu-stack
. This is part of elfutils again and is a command-line tool that can print stacktraces of any process. It useslibdw
internally. We could run it by catchingSIGILL
, callingfork()
thenexecve()
ingeu-stack
and capturing the output. I'm not really a fan of these out of process options though, and it would require users to install theelfutils
package. -
libcwd
- seems aimed at C++ and I couldn't spot easy to use API. Didn't spend much time looking at this one. -
Lastly I looked at
libbacktrace
. It's used as GCC's unwinder and is designed to run in and out of process. It's liberally licensed. It has a very simple (read: idiot-proof) API and is not much code in and of itself.
I decided to try and get libbacktrace
working. I vendored it into an SPM target (many hacks were done, as a PoC) and used its backtrace_print()
API. Here's my first results:
This Swift program (main.swift
):
import libbacktrace
@inline(never)
func f1() {
f2()
}
@inline(never)
func f2() {
f3()
}
@inline(never)
func f3() {
bt(CommandLine.arguments[0]) // call shim into vendored libbacktrace
}
f1()
outputs:
0x55b62337ca7a bt
/root/src/libbacktrace/Sources/libbacktrace/include/backtrace.h:189
0x55b62337c936 $s4test2f3yyF
/root/src/libbacktrace/Sources/test/main.swift:17
0x55b62337c8b8 $s4test2f2yyF
/root/src/libbacktrace/Sources/test/main.swift:12
0x55b62337c8a8 $s4test2f1yyF
/root/src/libbacktrace/Sources/test/main.swift:7
0x55b62337c7ba main
/root/src/libbacktrace/Sources/test/main.swift:20
0x7f2fe6278b96 ???
???:0
0x55b62336e629 ???
???:0
0xffffffffffffffff ???
???:0
So it works!
I think this could be a viable backend for our backtrace library. It requires no additional system packages to be installed, it is quick to build (just 13 C files), it can be vendored inside an SPM package, and it seems to give good stacktraces including function names, source files and line numbers.
I was using its simplest backtrace_print()
API - there are others which give more control and would enable us to feed the symbolicated function names to the Swift demangler. Together it could be quite nice.
What do people think?