この記事の要点
- Swift 5.9 では、プログラムがクラッシュしたときのデバッグ体験を大きく改善する クラッシュ時バックトレース 機能が入りました。クラッシュ時に、シェルからの「segmentation fault」のような不親切なメッセージではなく、関数名・ファイル名・行番号・該当ソースを表示する読みやすいバックトレースが出力されます。
- バックトレースは アウトオブプロセス(out-of-process) で生成されるため、クラッシュしたプロセス自身の壊れた状態に依存せず、より確実に情報を取得できます。Linux では既定で有効 です(macOS は手動で有効化、Windows は現時点で非対応)。
- ターミナルで実行している場合は、クラッシュ直後にプログラムを一時停止してデバッガをアタッチしたり、メモリやレジスタを調べたりできる 対話モード が使えます。
- バックトレーサは 構造化された並行処理(structured concurrency)に対応 しており、
async関数の呼び出し連鎖を正しくたどれます。再帰の制限・システムフレームのスキップ・自動 demangle など、読みやすさのための工夫も入っています。
背景: クラッシュ時に得られる情報が乏しかった
Swift 5.9 より前は、プログラムがクラッシュしても、親プロセス(多くの場合シェル)が「子プロセスがクラッシュした」と知らせるだけでした。
$ ./crash
I'm going to crash now
zsh: segmentation fault ./crash
Apple プラットフォームや Windows では OS 組み込みのクラッシュレポータが残したログを見られますが、Linux ではこのメッセージだけが頼りになりがちで、何が起きたのかをつかむのが困難でした。
何ができるのか
読みやすいバックトレースの出力
Swift 5.9 では、上のような不透明なメッセージの代わりに、クラッシュ原因・クラッシュしたスレッド・各フレームの関数名とソース位置、さらに該当行付近のソースコードまでを表示します。
*** Program crashed: Bad pointer dereference at 0x0000000000000004 ***
Thread 0 crashed:
0 0x00000001045a3df0 reallyCrashMe() + 404 in crash at /.../crash.swift:4:15
1 [ra] 0x00000001045a3ea4 crashMe() + 12 in crash at /.../crash.swift:8:3
2 [ra] 0x00000001045a3c50 main + 12 in crash at /.../crash.swift:11:1
3 [ra] [system] 0x000000018705d058 start + 2224 in dyld
レジスタの内容や読み込まれているイメージ(ライブラリ)の一覧も併せて出力されます。この機能は Linux では既定で有効 です。macOS でも有用ですが手動で有効化する必要があり、Windows は現時点では非対応です。
バックトレーサはクラッシュしたプロセスとは別のプロセスとして動作する アウトオブプロセス 方式です。クラッシュしたプロセス自身の壊れた状態に頼らないため、より確実にバックトレースを取得できます。
対話的なバックトレース
ターミナルに接続された状態(標準入力・標準出力がともにターミナル)でクラッシュすると、最終行に次のようなプロンプトが表示されます。
Press space to interact, D to debug, or any other key to quit (30s)
「クラッシュは起きたが再現できない」状況は厄介です。この機能は、クラッシュしたプログラムを一定時間(既定で 30 秒、設定で変更可能)一時停止し、その間に デバッガをアタッチ したり、プロセスを追加で調べたりする機会を与えます。
スペースキーを押すと簡単なコマンドプロンプトに入り、バックトレースの再生成、読み込み済みイメージの一覧、レジスタやメモリの内容表示、スレッド一覧の確認などができます。help で利用可能なコマンドを一覧できます。
>>> help
Available commands:
backtrace Display a backtrace.
bt Synonym for backtrace.
debug Attach the debugger.
exit Exit interaction, allowing program to crash normally.
help Display help.
images List images loaded by the program.
memory Inspect memory.
registers Display the registers.
set Set or show options.
thread Show or set the current thread.
debug コマンドまたは D キーでデバッガをアタッチでき、何が起きるかはプラットフォームに依存します。それ以外のキーを押すか 30 秒のタイマーが切れると、プログラムは通常どおりクラッシュします。
この対話モードは、標準入力・標準出力がともにターミナルに接続されているときだけ起動します。CI システムやスクリプトでは出力がパイプやファイルにリダイレクトされることが多いため、多くの場合は自動的に「対話モードを起動しない」挙動になります。明示的に無効化したい場合は、環境変数 SWIFT_BACKTRACE に interactive=no を設定します。色付き出力は color=no で無効化でき、interactive=no,color=no のように複数のオプションを組み合わせられます。
構造化された並行処理への対応
バックトレーサは並行処理を理解しており、async フレームを正しくさかのぼります。たとえば次のプログラムでは、
func level(n: Int) async {
if n < 5 {
await level(n: n + 1)
} else {
let ptr = UnsafeMutablePointer<Int>(bitPattern: 4)!
ptr.pointee = 42
}
}
@main
struct CrashAsync {
static func main() async {
await level(n: 1)
}
}
バックトレースは level(n:) の再帰呼び出しと、それを呼び出した CrashAsync.main() までを、async の活性化チェーン(activation chain)に沿って正しく表示します。通常のスタックを単純にたどると並行処理ランタイムの内部フレームが現れて分かりづらくなりますが、この機能はそれを避けてくれます。
Apple プラットフォームでは特別な準備は不要です。それ以外のプラットフォームでは、各フレームが async かどうかを判定するためにバックトレーサがシンボルを参照できる必要があります。必要なシンボルが無い場合は、async チェーンではなく通常のプログラムスタックをたどるため、並行処理ランタイムのフレームが表示されることがあります。
読みやすさのための工夫
- フレーム数の制御。 バックトレーサが生成するフレーム数の上限を設定できます(既定 64)。あわせて、スタック先頭側で取得するフレーム数も設定でき(既定 16)、再帰が深すぎてクラッシュした場合でも、膨大なフレームに埋もれることなく、再帰そのものとその原因の両方を確認できます。
- 不要なフレームのスキップ。 既定で、システムフレームと Swift の thunk をスキップします。これらは通常コンパイラやランタイムの開発者以外には関係なく、出力を分かりにくくするだけだからです。
- 自動 demangle。 Swift と C++ の両方の mangle された名前を自動的に demangle します。
使い方・注意点
- 有効・無効の切り替え。 Linux では既定で有効です。macOS では手動で有効化が必要で、Windows は現時点で非対応です。有効化や各種設定の詳細は、Swift コンパイラリポジトリの Backtracing ドキュメント を参照してください。
- 環境変数による設定。
SWIFT_BACKTRACE環境変数で挙動を調整します。interactive=noで対話モードを無効化、color=noで色付き出力を無効化でき、カンマ区切りで複数指定できます。 - CI・自動化での挙動。 対話モードは標準入力・標準出力がともにターミナルのときだけ起動するため、出力をリダイレクトする CI やスクリプトでは通常そのままで適切な(一時停止しない)挙動になります。
関連リンク
- Backtracing ドキュメント — 有効化方法や設定オプションの詳細
- この機能を含むリリースの全体像は Swift 5.9 リリース を参照してください。
- LLDB を使ったデバッグ自体の改善は Swift 5.9 のデバッグ改善 でまとめています。