# 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](#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:
```dart
@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:

```bash
$ python3 tools/build.py -a x64 -m release runtime runtime_precompiled

$ pkg/vm/tool/gen_kernel --platform out/ReleaseX64/vm_platform.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/dartaotruntime 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      _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:174)

$ out/ReleaseX64/dartaotruntime 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:

```bash
# 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:

```bash
# 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](https://pub.dev/packages/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:

```bash
# Using the unstripped ELF snapshot and piping all output to the tool's stdin.
$ out/ReleaseX64/dartaotruntime 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	_RawReceivePort._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/dartaotruntime 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	_RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:174)

# Saving all output to the file "output.txt".
$ out/ReleaseX64/dartaotruntime 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	_RawReceivePort._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	_RawReceivePort._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' objects
* `package:vm/dwarf/convert.dart`, for `PCOffset` 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](https://pub.dev/packages/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 with `LineSplitter` from `dart:convert` prior to
> transforming it with a `DwarfStackTraceDecoder`.

#### 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.
