Upgrade to Pro — share decks privately, control downloads, hide ads and more …

動作中のプログラムの中身をリアルタイムに覗く / Realtime Debugger for ...

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for Prota Prota
September 19, 2026

動作中のプログラムの中身をリアルタイムに覗く / Realtime Debugger for CSharp with Roslyn

C# Kaigi 2026(2026-09-19)の発表資料です。
副題: 実行を止めないデバッガのコンセプトと Roslyn 計装による C# 実装

プログラムを操作しながら、中で動いているコードを見られたら面白いと思いませんか。それを C# と Roslyn で作ってみました。

実行を止めず、コードも変えずに、動作中のプログラムを観測するデバッガのコンセプトと実装の話です。
・全部の文に「足あと」を残し、どこが動いたかをヒートマップで見渡す
・値は、詳しく見たいところに Tracepoint を置いて取る
・Roslyn でビルドに割り込み、1 文ごとに計装コードを挿入する(原本のソースは触らない)
・同じ観測を AI エージェントにも渡す

このデバッガはまだ公開していません。進捗はブログと YouTube でお知らせします。

ブログ: https://prota-p.com/
YouTube: https://www.youtube.com/@prota_pro
GitHub: https://github.com/prota-p
経歴・発表: https://prota-p.github.io/haruto-tanno/
X: https://x.com/prota_csharp

Avatar for Prota

Prota

September 19, 2026

Other Decks in Programming

Transcript

  1. 自己紹介 — プロ太(丹野 治門)— 博士(工学)、Microsoft MVP — NTT 研究所で 15

    年以上、研究開発(ソフトウェア工学・テスト自動化) — 個人事業主 2 年を経て、現在は株式会社モコベル(技術メンターなど) — C# のブログと YouTube「C# で学ぶプログラミング入門」 学習用OSS「MentorApp」(Blazor × クリーンアーキテクチャ × DDD)を公開 / 後半のデモ題材 github.com/prota-p/MentorApp C# Kaigi 2026 | 動作中のプログラムの中身をリアルタイムに覗く 2
  2. 小さな具体例 殴っても、敵が倒れない // 攻撃ボタンで、近くの敵にダメージを与える var damage = player.Power; if (!input.Attack)

    return; // 押していなければ何もしない if (input.Dash) damage *= 5; // ダッシュ中は 5 倍 foreach (var e in enemies) { if (Distance(player, e) > 5f) continue; // 射程外 if (e.IsInvincible) continue; // 無敵中 e.Hp -= damage; if (e.Hp <= 0) e.Dead(); } ダッシュ中に攻撃ボタンを 2〜3 回押せば倒せるはずの敵が、倒れない C# Kaigi 2026 | 動作中のプログラムの中身をリアルタイムに覗く 5
  3. 原因の候補は、たいてい 1 つではない // 攻撃ボタンで、近くの敵にダメージを与える var damage = player.Power; if

    (!input.Attack) return; // 押していなければ何もしない if (input.Dash) damage *= 5; // ダッシュ中は 5 倍 foreach (var e in enemies) { if (Distance(player, e) > 5f) continue; // 射程外 if (e.IsInvincible) continue; // 無敵中 e.Hp -= damage; if (e.Hp <= 0) e.Dead(); } 1 押しても false 2 ここを通っていない? 3 enemies が空? 4 距離が想定と違う 5 ここで弾かれている? どこを通ったのか、どんな値だったのか。読むだけでは絞りきれない 実際のコードはもっと大きい。最初に、どこを見ればいいのか分からないことも C# Kaigi 2026 | 動作中のプログラムの中身をリアルタイムに覗く 6
  4. 確かめる方法は、だいたい 2 つ 操作や時間で状態が変わっていくプログラムで、仮説を確かめるには 手段 困るところ ブレークポイント ① 止めると再現しないことがある ②

    止めるたびに操作の流れが切れる ログを仕込む ③ 仕込むのが面倒(変更 → ビルド) どちらも ④ 仕込んだところしか、見えない AI に任せても、確かめる主な手段はこの 2 つ(ログを足すか、ブレークポイントを置くか。VS / Rider の AI 連携も) ④ があるから、①②③ を何度も繰り返す C# Kaigi 2026 | 動作中のプログラムの中身をリアルタイムに覗く 7
  5. だったら、こうしたい 操作や時間で状態が変わっていくプログラムで、仮説を確かめるには 手段 困るところ どうしたいか ブレークポイント ① 止めると再現しないことがある → 止めない

    ② 止めるたびに操作の流れが切れる → 止めない ログを仕込む ③ 仕込むのが面倒(変更 → ビルド) → ビルドし直さない どちらも ④ 仕込んだところしか、見えない → 仕込まなくても、見えている ①②③ は、今の IDE でも軽くなります(トレースポイント・ホットリロード) ①②③ も解消したうえで、④ に取り組みたい C# Kaigi 2026 | 動作中のプログラムの中身をリアルタイムに覗く 8
  6. 「どこが動いているか」は、全部見えている A C D 足あと どこを通ったか A B B C

    SCOPE ファイル METHODS メソッド CODE 文 値 そこで何だったか アプリの動きに合わせて、足あとが光る。設定はゼロ D VALUES 変数の値 (値は、足あとで見当をつけてから設定します) C# Kaigi 2026 | 動作中のプログラムの中身をリアルタイムに覗く 9
  7. このデバッガの特徴 どうしたいか 特徴 止めない 動かしたまま、「足あと」と「値」を見る ビルドし直さない 足あとも値も、コードを触らずに見る。再ビルドも再起動もしない 仕込まなくても、見えている C# Kaigi

    2026 | 動作中のプログラムの中身をリアルタイムに覗く ファイル → メソッド → 文 の足あとは最初から見える 詳しく見たいところで初めて、Tracepoint を置く 11
  8. システム構成 ビルド時 実行時 計装ツール ソース ※ ① ビルドに割り込む 人 Roslyn

    ② コードを変換する 計装済みアプリ ↑ 設定を送る デバッガ UI ↓ ③ 実行時に送る デバッガ基盤 Windows アプリ HTTP — UI と同じ契約 AI 読む 書く 観測の記録 足あと・値・スクショ (※).NET Standard 2.1 で動くもの。ビルドは MSBuild(dotnet build) 例: ConsoleApp、WinForms、WPF、Blazor Server、WASM(一部制限あり)、Unity(クラスライブラリのみ) C# Kaigi 2026 | 動作中のプログラムの中身をリアルタイムに覗く 13
  9. 使う側から見ると ① ビルドに割り込む デバッグ対象のプロジェクトに足すもの 将来は NuGet パッケージ 1 つに(予定) <!--

    実行時: ライブラリを 1 つ参照する --> <ProjectReference Include="…/RealtimeDebugger.Instrumentation" /> <!-- ビルド時: 計装の targets を 1 つ import する --> <Import Project="…/RealtimeDebugger.Instrumentation.targets" /> アプリのコードは書き換えない。dotnet build するだけ dotnet build ソース 割り込む → 変換する → すり替える 原本は無変更 計装済みアプリ 中で起きていることは、次の枚 C# Kaigi 2026 | 動作中のプログラムの中身をリアルタイムに覗く 14
  10. 中で起きていること ① ビルドに割り込む 1. 割り込む 2. 変換する 3. すり替える BeforeBuild

    の直前 変換済みを obj/rtdg/ へ @(Compile) を 変換済みへ 4. コンパイル 標準のビルド 5. 戻す @(Compile) を 原本へ 3 の実物 ── Source Generator は既存のコードを書き換えられないので、一覧ごと差し替える <Target Name="SwitchToTransformedSource" BeforeTargets="BeforeBuild"> <ItemGroup> <Compile Remove="@(OriginalCompile)" /> <!-- 控えた原本の一覧 --> <Compile Include="$(RtdbWorkDir)\transformed\**\*.cs" /> </ItemGroup> </Target> 原本のソースは、1 バイトも変わらない C# Kaigi 2026 | 動作中のプログラムの中身をリアルタイムに覗く (ビルド時間への影響は後述) 15
  11. ② 1 文ごとに、これが挿入される 白がもとの 1 文。その前に、赤い 3 つが挿し込まれる var d

    = Emit(163, Instance); // 常に実行 if (d.ShouldCaptureTraceValues) // Tracepoint があるときだけ { EmitTraceValues(163, Instance, new object?[] { this, shell }); } ApplyWaitPoint(163, Instance, d); // 待つかは中で判定 Health.ApplyDamage(1); 足あと 値 実行制御 (読みやすく整えたイメージ。実物は文ごとにブロックで囲まれる。163 は文の番号 → 次の枚) C# Kaigi 2026 | 動作中のプログラムの中身をリアルタイムに覗く 16
  12. ② 変換のとき、決めていること Emit(163, Instance); ^^^ ① 文に番号をふる EmitTraceValues(163, Instance, new

    object?[] { this, shell }); ^^^^^^^^^^^^^^^ ② その時点で見えている変数を決める(Capture Set) 番号ごとの対応表(Instrumentation Map)を、アセンブリに埋め込む 接続時に、デバッガはアプリの対応表を読み込む 送られてくるデータ(文番号・値)を対応表と照合し、ソース上に表示する C# Kaigi 2026 | 動作中のプログラムの中身をリアルタイムに覗く 17
  13. 補足: 計装の方式 いまはソース変換、今後は IL も併用 ソース変換を選んだ理由 — 変数の捕捉は、ソース変換なら名前を書くだけ クロージャや async

    でフィールドに昇格した変数を、IL では自分で追うことになる — IL は、学ぶコストも、壊れたときに直すコストも高いと判断した 変換後のコードが C# なので、目で読んで確かめられる 今後は、Roslyn が計画を決め、IL が適用するハイブリッドへ .razor など、ソースを変換できない場所にも届く可能性 (Unity の Assets/ 直下も、Source Generator + IL 適用で計装できるかも。未検証) C# Kaigi 2026 | 動作中のプログラムの中身をリアルタイムに覗く 18
  14. ③ 足あとは、ビットを 1 つ立てるだけ 文 1 つ = 1 ビット。statementId

    が、そのままビットの位置 int[] の 1 語 = 32 文ぶん — 通ったら、そのビットを立てるだけ。ロックなし・確保ゼロ — すでに立っていれば、読むだけで抜ける — 一定間隔(0.1 秒)で、まとめてコピーして送る 全ての文で常時実行されるのは、このビットを立てる操作のみ C# Kaigi 2026 | 動作中のプログラムの中身をリアルタイムに覗く 19
  15. どれだけ遅くなるか 実行時(足あとの取得のみ) 計装なし 計装あり 倍率 Fib(20) × 5,000(計算だけ) 186 ms

    1,544 ms 8.3x 参考: Fib(20) × 5,000 ※ 3,002 ms 112,533 ms 37.5x 参考: Tanks 1.5 KLOC・1,000 フレーム ※ 5,747 ms 6,097 ms 1.06x ※ 2019 年の別実装(Tanno & Iwasaki, ICST 2019)。今回の実装でのアプリ計測はこれから。ゲームでは Fib より低いと予想 ビルド時(MentorApp 5.1 KLOC) 計装なし 計装あり 倍率 1 ファイル編集して再ビルド 4.1 秒 16.6 秒 4.1x 無変更で再ビルド 3.2 秒 16.7 秒 5.2x ビルドは増分判定が無く、毎回 4 プロジェクト全部を変換している。入れればもう少し改善の見込みあり C# Kaigi 2026 | 動作中のプログラムの中身をリアルタイムに覗く 20
  16. まとめ — 全部の文に、足あとを残す — 値は、詳しく見たいところに Tracepoint を置いて取る — Roslyn でビルドに割り込み、1

    文ごとに挿入する(原本は触らない) 動かしたまま、足あとをたどって値まで見に行ける C# Kaigi 2026 | 動作中のプログラムの中身をリアルタイムに覗く 21
  17. 最後に 学生のころから ずっと「デバッガ」として作ってきた(2008 / 2019 の論文も) 使っているうちに、バグ探しより、コードを理解するのに効くと感じ始めた いま思っていること 動いているコードに、自分の理解を合わせていく道具かもしれない 読んで分からなければ、代表的な操作を

    1 つ動かして、足あとを見渡す AI も同じものを見ているので、聞いたり、Tracepoint を置いてもらったりできる これから 自分で使って確かめる。C# 初心者向けの記事・動画でも試す 丹野 治門: “ゲームプログラムに適したリアルタイム性の高いデバッガの提案と実装”, 情報処理学会論文誌: プログラミング, Vol. 1, No. 2, pp. 42–56, 2008. Haruto Tanno, Hideya Iwasaki: “Suspend-less Debugging for Interactive and/or Realtime Programs”, ICST 2019, April 2019. C# Kaigi 2026 | 動作中のプログラムの中身をリアルタイムに覗く 22
  18. Thank you! プロ太(丹野 治門) @prota_csharp YouTube https://www.youtube.com/@prota_pro ブログ https://prota-p.com/ GitHub

    https://github.com/prota-p 経歴・発表 https://prota-p.github.io/haruto-tanno/ 今日のデバッガは、まだ公開していません。進捗はブログと YouTube で C# Kaigi 2026 | 動作中のプログラムの中身をリアルタイムに覗く 23
  19. 補足: 値の取り方(Tracepoint / Watch Path / Inspect) 値は「通るたび」に取るか、「後から 1 回」掘るか

    何を いつ・どこで いまの制限 Tracepoint その文で見えている 変数の値(要約 1 行) 通るたび、 その場のスレッドで 置いた後に通った分だけ 中のメンバは見ない Watch Path 式の値と直下のメンバ p.Cells["k"].Name 通るたび、 その場のスレッドで メンバ参照と添字だけ 条件・列挙・LINQ は不可 Inspect 記録に無い子を展開 最後に取った参照から 後から 1 回。 捕捉元スレッドに頼む 一貫性は保証しない 返事待ちは 1 秒まで ※ メンバ = public のプロパティ・フィールド。コレクションの要素は出ない — 探るのは Inspect、記録するのは Watch Path(1 操作で移せる) C# Kaigi 2026 | 動作中のプログラムの中身をリアルタイムに覗く 24