【SLDB】
EmacsでLispを動かしていて、 SLIMEが
固まったら?
(Common Lisp)

  • SLIMEが固まった状態は「SLDBが開いている」「REPLが返らない」「プロセスごと無応答」の3種類に分類でき、それぞれ対処法が異なります。
  • EmacsでCommon Lispを動かす環境はSBCL・Swank・SLIMEの3層で構成され、エラーが起きるとSLDBバッファがデバッガ情報を表示します。
  • SLDBではABORTで中止、USE-VALUEで代替値を渡して続行など、エラーの種類に応じたrestartを選んで処理を再開できます。
  • バックトレースのフレームをtで展開してローカル変数を確認し、vでソース位置に飛び、eでその場で式を評価するといったデバッグ操作が一通りEmacs上で完結します。
判断フロー:SLIMEが固まったら SLIME が反応しない 状態 A SLDB が開いている a 中止 1/2 代替値 c 警告無視 状態 B REPL が返らない C-c C-b → SLDB → a 状態 C 何も効かない slime-restart- inferior-lisp プロセス再起動 SLIME を終了したい 通常終了 (quit) slime-quit-lisp 接続だけ切る disconnect → reconnect可 キー早見表 a ABORT c CONT. 0-9 番号選択 C-c C-b 割り込み t 変数展開 v ソース e 評価 i inspect r 再実行 d pp表示 n / p フレーム移動

関連記事

1. SBCL・SLIME・SLDB の役割分担

「何がどこにあるか」を図で確認しましょう。

SBCL・SLIME・SLDB の役割分担 Emacs SLIME(Emacs パッケージ) ・REPL バッファ ・SLDB バッファ ◀ 今日の主役 ・コンパイル、ジャンプ等 Swank プロトコル SBCL(別プロセス = inferior-lisp) Swank サーバ (SLIMEが自動ロード) SBCL のデバッガ (*DEBUGGER-HOOK* で起動) エラー発生時のフロー 1 SBCLがコード実行 エラー発生 2 Swankが割り込み 情報をEmacsへ送信 3 SLIMEが受け取り SLDBバッファを表示 4 restartを選択して SBCLに命令を返す SLDB とは SBCLデバッガを Emacs上で 操作できる SLIMEの UI

SBCL が Lisp コードを実行します。
エラーが起きると SBCL 自身のデバッガが *DEBUGGER-HOOK* 経由で起動しようとしますが、そこに Swank が割り込んでデバッガの情報を Emacs に送ります1。Swank は SLIME がサーバ側に自動ロードするコンポーネントです。Emacs 側の SLIME がその情報を受け取って表示するのが SLDB バッファです。

SLDB は「SBCL のデバッガを Emacs 上で操作できるようにした SLIME のフロントエンド」と言えます2
操作は次の2レイヤーで考えると整理しやすくなります。

レイヤー担当操作の例
SBCL 側エラーの種類・restart の定義・バックトレースの生成エラーメッセージの内容、restart に何が出るか
Emacs/SLIME 側SLDB バッファの表示・キー操作・REPL との連携a q t v e などのキー

どの restart が出るかはエラーの種類によって SBCL が決めます。SLIME はその選択肢を画面に出して、キー操作を SBCL に伝えます3

1.1. SLIME の起動・終了と inferior-lisp

M-x slime を実行すると、Emacs が SBCL を inferior-lisp として子プロセスで起動し、Swank サーバに接続します4
*inferior-lisp* バッファがその生の出力先で、REPL バッファとは別物です。

SLIMEの起動・終了と inferior-lisp M-x slime 起動 SBCL 子プロセス 起動 Swankサーバ に接続 REPL 完成 主要バッファ *slime-repl sbcl* 普段使うREPL *inferior-lisp* SBCLの生出力(デバッグ用) 終了・接続操作 通常終了 (quit) / M-x slime-quit-lisp プロセス再起動 slime-restart-inferior-lisp 接続のみ切断 slime-disconnect

SLIME が使うバッファの対応は次のとおりです。

バッファ内容
*slime-repl sbcl*普段使う REPL
*inferior-lisp*SBCL プロセスの生の出力(デバッグ用)
*sldb …*デバッガバッファ
*slime-events*Swank の通信ログ(通常は不要)

終了方法は目的によって使い分けます。

REPL から Lisp を終了する場合は次のどちらかを使います。

CL-USER> (quit)
CL-USER> (sb-ext:exit)
Code language: CSS (css)

SBCL プロセスが終了し、SLIME の接続も切れます。Emacs コマンドから操作したい場合は M-x slime-quit-lisp でも同じ結果になります5

接続がおかしくなったときは M-x slime-restart-inferior-lisp を使います。SBCL プロセスを強制終了して新しく起動し直すので、作業中の定義はすべて消えます6

SBCL プロセスを残したまま Emacs との接続だけを切るには M-x slime-disconnect を使います。M-x slime-connect で再接続できます7

2. SLIME が反応しなくなる3つの状態

「SLIME が固まった」と感じる場面は、実際には3つに分類できます。

SLIMEが反応しなくなる 3つの状態 状態 A SLDB が開いている SLDB restart一覧 表示中 a キー → ABORT restartを選択 して解決 状態 B REPLが返らない 無限ループ C-c C-b slime-interrupt → SLDB へ 状態 C 何も効かない slime-restart- inferior-lisp プロセスごと 再起動

2.1. 状態A: エラーが出て SLDB が開いている

SBCL がデバッガ待ち状態です。SLDB バッファに restart 一覧が表示されています。
restart を選べば解決します。バッファを C-x k で閉じても SBCL 側の状態は変わらないため、REPL が戻りません8

2.2. 状態B: 計算中で REPL が返ってこない(SLDB も出ない)

無限ループや重い計算が走り続けています。SBCL はまだ正常で、デバッガにも入っていません。

C-c C-b、つまり M-x slime-interrupt を押します。
Emacs から SBCL プロセスに割り込みシグナルを送る操作です9。SBCL が割り込みを受け取ってデバッガに入り、SLDB が開きます。あとは状態A と同じ手順で対処できます。

CL-USER> (loop (format t "~%running"))   ; ← 返ってこない

C-c C-b を押す

interrupted
[Condition of type SB-SYS:INTERACTIVE-INTERRUPT]

Restarts:
 0: [CONTINUE] Continue from interrupted state.
 1: [ABORT] Return to SLIME's top level.

1 を選ぶか a を押して REPL に戻る
Code language: PHP (php)

CONTINUE はループを再開するので、ここでは選びません。

2.3. 状態C: Lisp プロセス自体がおかしい

接続が切れた、REPL バッファが全く応答しない、SLDB も出ない、かつ C-c C-b も効かない状態です。
M-x slime-restart-inferior-lisp で SBCL プロセスごと再起動します10

3. SLDB の基本構造と操作

SLDBの基本構造と操作キー *sldb* バッファ TYPE-ERROR: “hello” is not type NUMBER Restarts: 0: [ABORT] → top level 1: [USE-VALUE] … Backtrace: 0: (TWO-ARG-+ 1 “hello”) 1: (ADD-ONE “hello”) 2: (EVAL …) 3: … restartキー a ABORT c CONTINUE q 安全な状態へ 0-9 番号でrestart選択 フレームキー t 変数展開 v ソース位置を開く e フレーム内で評価 i インスペクタで開く エラー restart バックトレース

3.1. バッファの見方

The value "hello" is not of type NUMBER.
   [Condition of type TYPE-ERROR]          ← エラーの種類

Restarts:                                  ← 再開手段の選択肢
 0: [ABORT] Return to SLIME's top level.

Backtrace:                                 ← 呼び出し履歴
  0: (SB-KERNEL:TWO-ARG-+ 1 "hello")
  1: (ADD-ONE "hello")
  2: (SB-INT:SIMPLE-EVAL-IN-LEXENV ...)
  ...
Code language: PHP (php)

restart は SBCL が用意します。エラーの種類によって選択肢が変わります。a を押すと SLIME が「ABORT restart を選択」という命令を SBCL に送ります11

3.2. restart の主な種類

restart 名意味
ABORT現在の評価を中止してトップレベルへ戻る
CONTINUE処理を続ける(ワーニング等で有効)
USE-VALUE代わりの値を指定して続行する
STORE-VALUE値を変数に束縛して続行する
RETRY同じ操作を再試行する

迷ったら ABORT、つまり a を選べば安全に一段階戻れます12

3.3. 基本キー

restart を選ぶキーです。

キー操作
aABORT restart を実行する
qSLIME の安全な状態へ戻るリスタートを呼ぶ13
cCONTINUE restart を実行する
09番号で restart を選ぶ
RETカーソル位置の restart を実行する

3.4. restart の使い方:具体例

restart の使い方:具体例 ABORT 評価を中止してトップへ戻る (+ 1 “hello”) → TYPE-ERROR a CL-USER> プロンプトが戻る USE-VALUE 代替値で続行 (* undefined-var 10) → UNBOUND-VARIABLE 1 → 入力:5 (* 5 10) で続行 → 50 が返る STORE-VALUE なら 変数に束縛して継続 RETRY 同じ操作を再試行 (open “/tmp/no-file”) → FILE-ERROR ファイル作成 → 0 open を再実行 1: USE-VALUE で 別パスを指定も可

3.5. a で中止してトップレベルへ戻る

CL-USER> (+ 1 "hello")
Code language: JavaScript (javascript)
The value "hello" is not of type NUMBER.
   [Condition of type TYPE-ERROR]

Restarts:
 0: [ABORT] Return to SLIME's top level.
Code language: PHP (php)

a を押すと CL-USER> プロンプトが返ってきます14

3.6. USE-VALUE で代わりの値を渡して続行する

CL-USER> (* undefined-var 10)
Code language: JavaScript (javascript)
The variable UNDEFINED-VAR is unbound.
   [Condition of type UNBOUND-VARIABLE]

Restarts:
 0: [CONTINUE] Retry using UNDEFINED-VAR.
 1: [USE-VALUE] Use specified value.
 2: [STORE-VALUE] Set specified value.
 3: [ABORT] Return to SLIME's top level.
Code language: PHP (php)

1 を押してミニバッファに 5 を入力すると、(* 5 10) として続行して 50 が返ります。2STORE-VALUE を選ぶと、UNDEFINED-VAR に値がセットされて以降の評価でも使えます15

3.7. CONTINUE でワーニングを無視する

CL-USER> (defun example ()
           (let ((unused 42))
             :ok))
Code language: JavaScript (javascript)

コンパイル時にワーニングで SLDB が開くことがあります。c を押すか CONTINUE を選ぶと処理が続きます。

型エラーや未束縛変数エラーでは CONTINUE が出ないか、出ても意図しない動作になります。迷ったら a を選んでください16

3.8. RETRY でファイル操作を再試行する

CL-USER> (open "/tmp/no-such-file.txt")
Code language: JavaScript (javascript)
The file /tmp/no-such-file.txt does not exist.
   [Condition of type FILE-ERROR]

Restarts:
 0: [RETRY] Retry opening /tmp/no-such-file.txt.
 1: [USE-VALUE] Use an alternate file name.
 2: [ABORT] Return to SLIME's top level.
Code language: PHP (php)

ファイルを作成してから 0 を選ぶと再度 open が実行されます。1 を選んでミニバッファに別パスを入力すれば、そのファイルで続行します17

3.9. 判断フローまとめ

SLIME が反応しない
  ├─ SLDB バッファが開いている(状態A)
  │    ├─ 中止したい            → a (ABORT)
  │    ├─ 代替値で続行したい    → USE-VALUE / STORE-VALUE を番号で選ぶ
  │    └─ ワーニングを無視したい → c (CONTINUE)
  │
  ├─ REPL が返ってこない、SLDB が出ない(状態B)
  │    └─ C-c C-b (slime-interrupt) でデバッガに入る → a
  │
  └─ 何も効かない(状態C)
       └─ M-x slime-restart-inferior-lisp

SLIME を終了したい
  ├─ 通常: (quit) または M-x slime-quit-lisp
  └─ 接続だけ切る: M-x slime-disconnect
Code language: PHP (php)

4. デバッグ機能の使い方

デバッグ機能の使い方 バックトレースの読み方 (defun divide (a b) (/ a b)) (defun calculate (x) (divide x 0)) DIVISION-BY-ZERO 0: (/-MAYBE-TRUNCATE 10 0) 1: (DIVIDE 10 0) 2: (CALCULATE 10) 3: (EVAL …) フレーム0が最も内側 上から原因をたどれる キー操作フロー 1 t ローカル変数を展開 A=10 B=0 ← 原因確認 2 v ソース行にジャンプ 3 e フレーム内で評価 (* a 2) → 20 4 a ABORT → 修正へ

フレームを調べるキーです。

キー操作
tローカル変数と引数を展開・折りたたむ
vそのフレームのソースコード位置を開く
eフレームの環境で式を評価する
dフレームの環境で式を評価しプリティプリントする
iフレームの環境で式を評価しインスペクタで開く
r同じ引数でフレームを再実行する(実装依存)18
Rミニバッファの値をフレームの戻り値として返す(実装依存)
n pフレーム間を移動する
M-n M-pフレーム間を移動しつつソースを開く

4.1. バックトレースを読む

(defun divide (a b)
  (/ a b))

(defun calculate (x)
  (divide x 0))
CL-USER> (calculate 10)
arithmetic error DIVISION-BY-ZERO signalled
   [Condition of type DIVISION-BY-ZERO]

Restarts:
 0: [ABORT] Return to SLIME's top level.

Backtrace:
  0: (SB-KERNEL:/-MAYBE-TRUNCATE 10 0)
  1: (DIVIDE 10 0)
  2: (CALCULATE 10)
  3: (SB-INT:SIMPLE-EVAL-IN-LEXENV ...)
  ...
Code language: PHP (php)

フレーム番号 0 が最も内側、つまりエラー直前です。フレーム 1 が DIVIDE、フレーム 2 が CALCULATE なので、CALCULATE0 を渡したことが原因とわかります19

4.2. t でローカル変数を展開する

フレームの行で t を押すと引数とローカル変数が表示されます20

  1: (DIVIDE 10 0)
     Locals:
       A = 10
       B = 0

4.3. v でソース位置を開く

フレームにカーソルを置いて v を押すと、そのフレームのソース行にジャンプします。どのファイルのどの行かをすぐ確認できます21

4.4. e でフレームの環境で式を評価する

フレームにカーソルを置いて e を押し、ミニバッファに式を入力します。そのフレームのローカル変数が有効な状態で評価されます。フレーム 1: (DIVIDE 10 0)e を押して (* a 2) と入力すると 20 が返ります22

4.5. d でプリティプリントする

de と同じですが、結果を別バッファにプリティプリントして表示します。ネストの深いリストを読むときに使えます。

4.6. i でインスペクタに渡す

i を押してミニバッファで式を入力すると、評価結果をインスペクタで開きます。リストや構造体の中身を掘り下げて見たいときに使います。インスペクタのスロットで RET を押すとさらに深い構造に入れます。q で SLDB に戻ります23

4.7. r でフレームを再実行する

C-c C-c でフレームの関数を修正・再コンパイルしてから r を押すと、同じ引数でそのフレームだけ再実行します。最初から呼び直さずにその場で修正を試せます。SBCL では使えますが、実装によっては利用できない場合があります24

4.8. 通しのデバッグ例

(defun process-items (items)
  (mapcar (lambda (x) (* x 2)) items))
CL-USER> (process-items '(1 2 "three" 4))
The value "three" is not of type NUMBER.
   [Condition of type TYPE-ERROR]

Restarts:
 0: [ABORT] Return to SLIME's top level.

Backtrace:
  0: (SB-KERNEL:TWO-ARG-* "three" 2)
  1: ((LAMBDA (X)) "three")
  2: (MAPCAR #<FUNCTION (LAMBDA (X))> ("three" 4))
  3: (PROCESS-ITEMS (1 2 "three" 4))
  ...
Code language: PHP (php)
  1. フレーム 1t を押して X = "three" を確認します
  2. フレーム 3t を押して ITEMS = (1 2 "three" 4) を確認します。リストに文字列が混入しています
  3. a で ABORT して REPL に戻り、コードを修正します25
(defun process-items (items)
  (mapcar (lambda (x)
            (if (numberp x)
                (* x 2)
                (error "Not a number: ~A" x)))
          items))
Code language: JavaScript (javascript)
  1. Swank は接続確立時に call-with-debugger-hook を通じて swank-debugger-hook*DEBUGGER-HOOK* にセットします。これにより SBCL のネイティブデバッガの代わりに SLDB が起動するようになります。 – swank.lisp (slime/slime GitHub)
  2. デフォルトでは SLIME が起動した Lisp プロセスのメインスレッドのデバッグを担いますが、swank:*global-debugger*t にすると全スレッドのエラーを SLDB で扱えるようになります。 – Other configurables (SLIME User Manual, version 2.24)
  3. SLIME は SBCL 以外にも Clozure CL、CLISP、ECL、Allegro CL など多くの Common Lisp 実装に対応しています。ただし r(フレーム再実行)など一部の機能は実装によって使えない場合があります。 – SLIME – WikEmacs
  4. 起動する Lisp 実装は slime-lisp-implementations 変数で切り替えられます。例:(setq slime-lisp-implementations '((sbcl ("sbcl")) (ccl ("ccl")))) と設定すると、M-- M-x slime で起動する実装を選べるようになります。 – SLIME User Manual, version 2.32
  5. (quit) は SBCL 独自の関数です。複数の Lisp 実装に対応するコードを書く場合は (uiop:quit) を使うのがポータブルです。uiop は ASDF に同梱されているため追加インストールは不要です。 – Scripting – Common Lisp Cookbook
  6. (sb-ext:exit):code キーワード引数で終了コードを指定できます。例えば (sb-ext:exit :code 1) はエラー終了を意味します。スクリプトや CI 環境で終了コードを制御したいときに使います。 – SBCL User Manual
  7. Swank サーバをリモートマシンで起動して SSH トンネル経由で接続する使い方もできます。ローカルの Emacs から M-x slime-connect でホストとポートを指定するだけで、リモート Lisp 環境をそのままデバッグできます。 – SLIME User Manual, version 2.32
  8. SLDB の中でさらに式を評価してエラーが起きると、デバッガがネストして開きます。バッファ先頭に [2] のようにネスト深度が表示されます。q を押すか、restart 一覧の「Return to SLIME’s top level」を選ぶと一番外に戻れます。 – CLiki: SLIME Features
  9. C-c C-b は SLIME マニュアルに記載されている slime-interrupt のデフォルトキーバインドです。REPL バッファ内で有効です。 – Restarts (SLIME User Manual, version 2.24)
  10. slime-restart-inferior-lisp は inferior-lisp プロセスを強制終了して再起動するため、REPL で定義した関数や変数はすべて消えます。重要な定義はファイルに保存してから実行してください。 – SLIME User Manual, version 2.32
  11. バックトレースの表示形式は swank:*sldb-printer-bindings* で制御できます。例えば (push '(*print-pretty* . t) swank:*sldb-printer-bindings*)~/.swank.lisp に書くと、バックトレースがプリティプリントされて読みやすくなります。 – Other configurables (SLIME User Manual, version 2.24)
  12. restart は restart-case マクロを使ってユーザが独自に定義できます。例えばファイル読み込み関数の中で「別のファイル名を試す」restart を定義しておくと、エラー時に SLDB の選択肢に現れます。 – Beyond Exception Handling: Conditions and Restarts – Practical Common Lisp
  13. q が呼ぶリスタートは swank:*sldb-quit-restart* 変数で変更できます。マルチスレッドアプリでスレッドを終了させたいときは (setf swank:*sldb-quit-restart* 'sb-thread:terminate-thread) のように設定します。 – Other configurables (SLIME User Manual, version 2.24)
  14. ABORT は Common Lisp 標準で定義された restart 名です。実装は常にトップレベルに ABORT restart を用意することが推奨されており、SBCL では評価リクエストの外側に必ず存在します。 – 29.4.7. Establishing Restarts (CLtL2)
  15. USE-VALUESTORE-VALUE はどちらも Common Lisp 標準で定義されている restart 名です。USE-VALUE は値をその場だけ使い、STORE-VALUE は変数や場所に値を格納して続行します。 – Condition System – Common Lisp Docs
  16. ワーニングを自動的に無視したい場合は (muffle-warning) 関数や handler-bind との組み合わせでコード側から制御できます。コンパイル時の特定ワーニングを静かにするには sb-ext:muffle-conditions デクレアも使えます。 – SBCL User Manual
  17. restart-case を使って独自の RETRY restart を定義することもできます。例えばデータベース接続関数の中に「接続をリトライする」restart を定義しておくと、接続エラー時に SLDB から直接リトライを選べます。 – Common Lisp: A Tutorial on Conditions and Restarts
  18. SLIME マニュアルでは「This command is not available in all implementations」と明記されています。SBCL では使用可能です。 – Miscellaneous (SLIME User Manual, version 2.24)
  19. バックトレースの SB-KERNELSB-INT で始まるフレームは SBCL の内部実装です。通常はこれらより番号の大きいフレーム(ユーザコード側)から原因を探します。 – Debugging – Common Lisp Cookbook
  20. t を押すと CATCH タグも表示されます。catch フォームを使ったコードをデバッグするとき、どの catch が有効かを確認できます。 – Examining frames (SLIME User Manual, version 2.24)
  21. v でジャンプできるのは、ソース情報が保存された状態でコンパイルされた定義に限ります。C-c C-cslime-compile-defun)でコンパイルした関数であればデバッグ情報が含まれます。ファイルをロードするだけではジャンプ先が得られない場合があります。 – Debugging – Common Lisp Cookbook
  22. e は結果をミニバッファの echo area に表示します。d は同じ評価を行いますが、結果を別の一時バッファにプリティプリントして開きます。ネストの深い構造を確認するときは d のほうが読みやすくなります。 – Examining frames (SLIME User Manual, version 2.24)
  23. SLIME のインスペクタは M-x slime-inspect でデバッガの外からも起動できます。ミニバッファで任意の式を入力してその値を調べることができ、SLDB を経由しなくても使えます。 – SLIME User Manual version 2.24
  24. SLDB の中から C-c C-c を押すと、バックトレースに表示されている関数の定義を再コンパイルできます。再コンパイル後に r でそのフレームを再実行することで、プログラム全体を最初から動かし直さずにバグ修正を確認できます。 – Debugging – Common Lisp Cookbook
  25. Common Lisp の条件システム全体については Peter Seibel 著 Practical Common Lisp 第19章「Beyond Exception Handling: Conditions and Restarts」で詳しく解説されています。同書はオンラインで無料公開されています。 – Beyond Exception Handling: Conditions and Restarts – Practical Common Lisp