Using and interpreting DWARF stack traces
The Dart VM AOT compiler can encode code source mapping information as DWARF
debugging information instead of using in-snapshot CodeSourceMap
objects.
Unlike CodeSourceMap
objects, this DWARF information can then be stripped
from snapshots provided to users, which lowers the binary size.
Turning on DWARF encoding of code source mapping information
To output code source mapping information as DWARF debugging information,
use the --dwarf-stack-traces
flag. By default, generated assembly or ELF
snapshots include the DWARF information directly.
Note: DWARF debugging information is not obfuscated when using the
--obfuscate
flag. To avoid unobfuscated information leaking to users when using--dwarf-stack-traces
, use the--strip
option when creating ELF snapshots.
To output DWARF debugging information to a separate file that can be saved
as an artifact for later debugging, use the --save-debugging-info=<...>
flag.
The generated file is not guaranteed to be any specific format and may change in
the future. See
Translating DWARF stack traces for how to use
this file.
Differences when using DWARF code source mapping information
When using DWARF code source mapping information, stack traces only include PC address information for frames. This means that a developer will need the generated DWARF information to get function, file, and line number information for each frame.
Take the following example program, saved as throws.dart
in the root of a
Dart SDK checkout:
@pragma('vm:prefer-inline')
void bar() => throw null;
@pragma('vm:never-inline')
void foo() => bar();
void main() => foo();
Below is the result of running the file both without and with
--dwarf-stack-traces
in a 64-bit Linux development environment:
$ python3 tools/build.py -a x64 -m release runtime runtime_precompiled
$ pkg/vm/tool/gen_kernel --platform out/ReleaseX64/vm_platform_strong.dill -o throws.dill throws.dart
$ out/ReleaseX64/gen_snapshot --snapshot_kind=app-aot-elf --elf=snapshot.so throws.dill
# Here, we save the debugging information to a separate file debug.data as well
# as including it in the generated ELF snapshot for future examples.
$ out/ReleaseX64/gen_snapshot --dwarf-stack-traces --save-debugging-info=debug.data --snapshot_kind=app-aot-elf --elf=dwarf_snapshot.so throws.dill
$ out/ReleaseX64/dart_precompiled_runtime snapshot.so
Unhandled exception:
Throw of null.
#0 bar (file:///.../sdk/throws.dart:2)
#1 foo (file:///.../sdk/throws.dart:5)
#2 main (file:///.../sdk/throws.dart:7)
#3 _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:307)
#4 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:174)
$ out/ReleaseX64/dart_precompiled_runtime dwarf_snapshot.so
Unhandled exception:
Throw of null.
Warning: This VM has been configured to produce stack traces that violate the Dart standard.
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
pid: 148677, tid: 139648739401792, name Dart_Initialize
isolate_instructions: 7f028072e000 vm_instructions: 0
#00 abs 00007f028098381e virt 000000000025c81e /.../sdk/snapshot.so
#01 abs 00007f0280983742 virt 000000000025c742 /.../sdk/snapshot.so
#02 abs 00007f02809837d5 virt 000000000025c7d5 /.../sdk/snapshot.so
#03 abs 00007f028099d8af virt 00000000002768af /.../sdk/snapshot.so
#04 abs 00007f02808f42ff virt 00000000001cd2ff /.../sdk/snapshot.so
#05 abs 00007f028099d7a2 virt 00000000002767a2 /.../sdk/snapshot.so
#06 abs 00007f028086df98 virt 0000000000146f98 /.../sdk/snapshot.so
For a DWARF-based stack trace, we are guaranteed to have an absolute PC
address for each frame (the hexadecimal number following abs
) as well as the
absolute addresses for the start of the isolate instructions and the VM
instructions (the line starting with isolate_instructions
). This information
is used by our tool and libraries for converting DWARF stack traces, described
later.
If we are running from a snapshot which is a native format for dynamic libraries
(e.g., ELF on Linux or Mach-O on Mac OS X), then we will also have a virtual PC
address for each frame (the hexadecimal number following virt
). If we have
appropriate DWARF information for the snapshot (e.g., part of the unstripped
ELF snapshot on Linux, or a separately generated .DSYM package when compiling
the generated assembly on Mac OS X), we can use the virtual address along with
the DWARF information to get back function, file, and line number information
using native tools:
# Virtual address from frame #00
$ addr2line -f -i -e dwarf_snapshot.so 000000000025c81e
bar
file:///.../sdk/throws.dart:2
foo
file:///.../sdk/throws.dart:5
# Virtual address from frame #01
$ addr2line -f -i -e dwarf_snapshot.so 000000000025c742
Precompiled____main_6919
file:///.../dart/sdk/throws.dart:7
However, as seen here, the information may not be exactly as expected from the
non-DWARF stack trace. In addition, the DWARF stack trace may include frames
that are internal to the Dart VM and would not normally be provided in
non-DWARF stack traces. Note that there are seven frames in the DWARF stack
trace above, but only 5 in the non-DWARF stack trace. Frame #02
happens
to correspond to one of these elided frames:
# Virtual address from frame #02
$ addr2line -f -i -e dwarf_snapshot.so 000000000025c7d5
Precompiled____main_main_6920
file:///.../sdk/throws.dart:?
Translating DWARF stack traces
To ease translation of DWARF stack traces, we provide a platform-independent
tool and libraries. They can translate DWARF stack traces using the DWARF
debugging information contained in unstripped ELF snapshots or saved separately
using --save-debugging-info=<...>
. For most uses, the tool should suffice, but
the libraries it uses are also available for integration into Dart-based
workflows.
Using the stack trace converter tool
A simple way to translate DWARF stack traces is to use the tool decode
from the package native_stack_traces. The tool has one required
argument -e
, which takes the name of the file containing DWARF
debugging information as an input. This can either be an
unstripped ELF snapshot or a file generated by --save-debugging-info=<...>
.
Using the earlier example, we can run the snapshot and convert any generated stack traces as follows:
# Using the unstripped ELF snapshot and piping all output to the tool's stdin.
$ out/ReleaseX64/dart_precompiled_runtime dwarf_snapshot.so |& out/ReleaseX64/dart pkg/native_stack_traces/bin/decode.dart -e dwarf_snapshot.so
Unhandled exception:
Throw of null.
Warning: This VM has been configured to produce stack traces that violate the Dart standard.
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
pid: 158029, tid: 140495595888704, name Dart_Initialize
isolate_instructions: 7fc7ad076000 vm_instructions: 0
#0 bar (file:///.../sdk/throws.dart:2)
#1 foo (file:///.../sdk/throws.dart:5)
#2 main (file:///.../sdk/throws.dart:7)
#3 _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:307)
#4 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:174)
# Using the separately saved debugging information and piping all output to the tool's stdin.
$ out/ReleaseX64/dart_precompiled_runtime dwarf_snapshot.so |& out/ReleaseX64/dart pkg/native_stack_traces/bin/decode.dart -e debug.data
Unhandled exception:
Throw of null.
Warning: This VM has been configured to produce stack traces that violate the Dart standard.
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
pid: 158242, tid: 139697980215360, name Dart_Initialize
isolate_instructions: 7f0df76e1000 vm_instructions: 0
#0 bar (file:///.../sdk/throws.dart:2)
#1 foo (file:///.../sdk/throws.dart:5)
#2 main (file:///.../sdk/throws.dart:7)
#3 _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:307)
#4 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:174)
# Saving all output to the file "output.txt".
$ out/ReleaseX64/dart_precompiled_runtime dwarf_snapshot.so >output.txt 2>&1
# Reading the input to convert from the file "output.txt" instead of stdin.
$ out/ReleaseX64/dart pkg/native_stack_traces/bin/decode.dart -e debug.data -i output.txt
Unhandled exception:
Throw of null.
Warning: This VM has been configured to produce stack traces that violate the Dart standard.
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
pid: 159139, tid: 139991440654400, name Dart_Initialize
isolate_instructions: 7f524b090000 vm_instructions: 0
#0 bar (file:///.../sdk/throws.dart:2)
#1 foo (file:///.../sdk/throws.dart:5)
#2 main (file:///.../sdk/throws.dart:7)
#3 _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:307)
#4 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:174)
# Output the converted input to the file "converted.txt" instead of stdout.
$ out/ReleaseX64/dart pkg/native_stack_traces/bin/decode.dart -e debug.data -i output.txt -o converted.txt
$ cat converted.txt
Unhandled exception:
Throw of null.
Warning: This VM has been configured to produce stack traces that violate the Dart standard.
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
pid: 159139, tid: 139991440654400, name Dart_Initialize
isolate_instructions: 7f524b090000 vm_instructions: 0
#0 bar (file:///.../sdk/throws.dart:2)
#1 foo (file:///.../sdk/throws.dart:5)
#2 main (file:///.../sdk/throws.dart:7)
#3 _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:307)
#4 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:174)
Note: As seen here, only lines that contain stack trace frames are converted. In particular, we do not strip the extra stack trace header lines that are only part of DWARF-based stack traces.
Using the stack trace converter libraries
This section describes two different libraries used to retrieve and convert information associated with DWARF-based stack traces:
package:vm/dwarf/dwarf.dart
, for 'Dwarf' and 'CallInfo' objectspackage:vm/dwarf/convert.dart
, forPCOffset
objects and various operations on stack traces
DWARF debugging information
A Dwarf
object represents the DWARF debugging information from either
unstripped ELF snapshots or a file generated by --save-debugging-info=<...>
.
The Dwarf.fromFile
factory takes a filename and returns a 'Dwarf' object,
if the given file exists and is a recognized format that contains DWARF
information.
Call site information
A CallInfo
object represents a call site in the code corresponding to a
particular virtual address and contains the function name, file name, and line
number for the call site and whether the code for the call site has been
inlined at this use.
To look up the call information associated with a particular virtual address,
use Dwarf::callInfo
. If the virtual address is outside the range of those
generated by the DWARF line number program(s), then it returns null
, otherwise
it returns an iterable of CallInfo
objects. If the optional
includeInternalFrames
argument is false (the default), then the iterable can
be empty if the virtual address points to code that does not correspond to
user or library code, like generated function prologues.
Converting stack traces
To convert a stream of lines that may include DWARF stack traces, use
the stack transformer DwarfStackTraceDecoder
. Its constructor takes a Dwarf
object, and the transformer, like decode.dart
from the package native_stack_traces, only changes
lines that correspond to stack trace frames.
Note: The stack transformer assumes that lines are not combined or broken across
String
s in the input stream. If this is not already guaranteed, transform the stream withLineSplitter
fromdart:convert
prior to transforming it with aDwarfStackTraceDecoder
.
Extracting information from stack traces
A PCOffset
object represents the PC address information extracted from a
DWARF-based stack trace frame. It contains whether the PC address comes from
the VM or isolate instructions section, and the offset of the PC address in
that section.
The PCOffset::virtualAddress
method takes a Dwarf
object and returns
a compatible virtual address for use with methods on that Dwarf
object.
The function collectPCOffsets
extracts PCOffset
s for the frames of a stack
trace.
Note: Since the absolute addresses of the VM and isolate instruction sections are part of the stack frame header,
collectPCOffsets
can only extract information from complete stack traces.