デバッグ中に Visual Studio が本来のソースではなく IL の逆コンパイル画面を開き、「External Source」と表示されてブレークポイントが効かない――.NET/C# 開発で頻発するこの症状は、たいてい数個の設定とキャッシュ整理で解消できます。本記事では最短手順から根本原因の切り分け、チームで再発させない運用 tips までを一気通貫で解説します。
Visual Studio が自動でデコンパイルし「External Source」となる症状と背景
該当プロジェクトには 95 個のソースファイルが存在し、通常であればデバッガはこれらのファイルを開いて実行位置を表示します。しかし一部の環境では、該当モジュール(EXE/DLL)のシンボル(PDB)が正しく照合されず、Visual Studio が「自分のコード」と認識できないため、IL からの自動逆コンパイルビューを開いてしまいます。その結果、該当行にブレークポイントを設定できない、または「未バインド(Unbound)」のまま有効にならない、といった現象が発生します。
質問概要(再掲・要点)
- 現象:デバッグ中に対象ファイルが「External Source」と扱われ、IL の逆コンパイルビューが開く。ブレークポイントが設定・ヒットできない。
- 環境:Visual Studio(C#/.NET を想定、バージョンは特定せず)。
最短で直す結論(まずはこれだけ試す)
- Just My Code を有効化し、自動デコンパイルを無効化
[ツール]→[オプション]→[デバッグ]→[全般] にて、- 「自分のコードのみ有効にする(Just My Code)」にチェック
- 「必要に応じて自動的にソースをデコンパイルする(Managed only)」のチェックを外す
- キャッシュを削除して再ビルド
Visual Studio をすべて終了し、プロジェクトのルートで以下を削除。.vs/ bin/ obj/その後、Visual Studio を再起動して クリーン → ビルド を実行します。 - PDB の生成と読み込みを確認
- ビルド構成が Debug であること
- プロジェクトの プロパティ → ビルド で「デバッグ情報を出力」が フル または ポータブル
- デバッグ実行中に [デバッグ]→[ウィンドウ]→[モジュール] を開き、対象モジュールの「シンボルの状態」が「読み込まれた」
- シンボルサーバー/Source Link を一時的に無効化
[ツール]→[オプション]→[デバッグ]→[シンボル] で外部シンボルパスを外し、ローカル PDB を最優先にします。Source Link を使うプロジェクトでは、設定が衝突して同一パスの別バージョンを拾わないか検証してください。 - それでもダメなら、新しいソリューションにインポートし直す/Visual Studio の修復・最新版更新を検討。
なぜ「External Source」扱いになるのか:メカニズムの理解
Visual Studio は、実行中に読み込まれた各アセンブリ(EXE/DLL)と、そのアセンブリに紐づく PDB の一意な識別子(署名/タイムスタンプ/ハッシュなど)を照合します。照合に成功し、かつデバッガ設定が「自分のコード」を優先するようになっていれば、エディタは プロジェクト内の .cs ファイルを開きます。
一方、以下のいずれかに該当すると、VS は「元ソースと一致する PDB を取得できない」と判断し、IL の逆コンパイルビューを表示します。
- PDB が生成されていない/
binとobjの PDB が古い - 実行時にロードされる DLL が 別のビルド出力(例:古い
publishディレクトリ)で、PDB と組が合っていない - Source Link/シンボルサーバーから 別バージョンの PDB が拾われた
- IL 変換(Fody/PostSharp などの Weaving)や難読化により、PDB の署名が合わない
- Docker/WSL/リモートデバッグで ソースパスがマップされていない
- Release/最適化ビルドで、該当行が最適化により消えている
設定・運用の標準化テンプレート
| 項目 | 場所 | 推奨値 | 狙い |
|---|---|---|---|
| Just My Code | ツール → オプション → デバッグ → 全般 | 有効 | 自分のソース優先で逆コンパイルを防ぐ |
| 自動デコンパイル(Managed only) | ツール → オプション → デバッグ → 全般 | 無効 | 誤って IL ビューに遷移しないようにする |
| デバッグ情報 | プロジェクト プロパティ → ビルド | フル / ポータブル | PDB の生成・一致性を担保 |
| 最適化 | プロジェクト プロパティ → ビルド | Debug では無効 | ブレークポイントの欠落を防ぐ |
| シンボルの場所 | ツール → オプション → デバッグ → シンボル | まずはローカルの PDB だけ | 外部の古い PDB を拾う誤動作を排除 |
| JIT 最適化の抑止 | ツール → オプション → デバッグ → 全般 | 有効(該当項目) | 最適化で行が消える問題を軽減 |
| Source Link | ツール → オプション → デバッグ | 一時的に無効(切り分け時) | 衝突・混同の有無を確認 |
手順詳細:確実にブレークポイントを「バインド」させる
Just My Code & 自動デコンパイル設定
- [ツール]→[オプション]→[デバッグ]→[全般] を開く。
- 「自分のコードのみ有効にする(Just My Code)」にチェック。
- 「必要に応じて自動的にソースをデコンパイルする(Managed only)」のチェックを外す。
- (推奨)「モジュール読み込み時に JIT 最適化を抑制(Managed のみ)」にもチェック。
キャッシュのクリーンアップと再ビルド
VS のプロセスを完全に終了してから、プロジェクトルートでキャッシュフォルダを削除します。
.vs/
bin/
obj/
再度ソリューションを開き、クリーン → ビルド → デバッグ開始 の順で実行します。
PDB の生成と一致性の検証
- Debug 構成になっているか(ツールバーまたは[ビルド構成マネージャー])。
- プロジェクト プロパティ → ビルド → デバッグ情報を出力 が フル または ポータブル。
- デバッグ中に [デバッグ]→[ウィンドウ]→[モジュール] を開き、対象モジュールの シンボルの状態 を確認。
未読み込み の場合は右クリック → シンボルの読み込み から正しい PDB パスを明示的に指定します。
シンボルサーバーと Source Link の切り分け
複数のバージョンの PDB がシンボルサーバー上にあると、VS は「最初にヒットした PDB」を拾う場合があります。まずは ローカル PDB のみ に限定して正しくバインドできるかを検証し、安定したらシンボルサーバーを再度有効化します。Source Link 利用時は、コミット ID と PDB が一致しているか(CI のキャッシュやサブモジュール更新漏れがないか)を確認してください。
根本原因別トラブルシュート
① PDB 不一致(もっとも多い)
症状:「ブレークポイントが現在の場所にバインドされていません」や「ソースが変更されました」のメッセージ。
確認:モジュールウィンドウの「バージョン」「シンボルの状態」「PDB のパス」「ソースのパス」を確認。
対処:bin/obj を削除して再ビルド。実行中の EXE/DLL の配置場所(IIS Express、テストホスト、dotnet run の作業ディレクトリ、コンテナのボリューム)と PDB のペアが合っているかを突き止めます。
② 別出力物の誤実行(publish/古いコピー)
症状:ソリューションは新しくビルドしているのに、デバッガが古い DLL にアタッチしている。
確認:モジュールウィンドウの「パス」に 想定外のディレクトリ が出ていないか。
対処:起動プロファイル(プロジェクト/IIS/実行可能ファイル)を見直し、実行しているパス = ビルドしたパス を徹底。
③ IL Weaving/難読化ツールの影響
Fody、PostSharp、難読化ツールなどが ビルド後に IL を書き換える と、PDB の署名や行情報がズレます。
対処:Debug 構成では Weaving/難読化をオフにする条件付き設定にし、Release のみ有効化する運用を推奨します。
④ Docker/WSL/リモートデバッグのパスマッピング
コンテナ内や WSL 上で動くプロセスは、コンテナ内のパス と ホストのソースパス が異なるため、適切なマッピングが必要です。
対処:デバッグプロファイルでマップを構成し、「ドキュメントパスの一致」 が取れるようにします。マルチステージビルドでソースをコピーしていないケースも要注意。
⑤ Release/最適化の影響
Release では <Optimize>true</Optimize> が一般的で、特に インライン化 や 無駄なコード除去 により、ブレークポイント位置の IL が存在しない場合があります。
対処:デバッグ用は Debug 構成 を使用し、必要に応じて「モジュール読み込み時に JIT 最適化を抑止」を有効化。
⑥ マルチターゲット/別フレームワークの混同
net8.0 と net6.0 のように複数ターゲットをビルドしていると、起動時に 別ターゲットの DLL を読み込んで PDB が一致しない場合があります。
対処:起動プロジェクト/ランタイム識別子(RID)/テストプロジェクトのターゲットを確認。
CSProj の推奨設定例(Debug)
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<DebugType>portable</DebugType> <!-- クロス環境でも扱いやすい -->
<DebugSymbols>true</DebugSymbols>
<Optimize>false</Optimize>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<Deterministic>true</Deterministic>
</PropertyGroup>
既存のレガシープロジェクトで フル PDB を使う場合は <DebugType>full</DebugType> を選択しても構いません。チーム横断では portable で統一しておくと、CI やコンテナ、クロスプラットフォームでも挙動が安定します。
モジュールウィンドウを使った現場での即時診断
- デバッグ開始後、[デバッグ]→[ウィンドウ]→[モジュール] を開く。
- 対象 DLL/EXE を探し、以下を確認。
- シンボルの状態:読み込まれた / なし / 不一致
- パス:想定の
bin/Debug/...になっているか - バージョン:古い生成物が混ざっていないか
- 右クリック → シンボルの読み込み で PDB を明示指定し、ステータスが「読み込まれた」になるかを確認。
チームで再発させないチェックリスト
- リポジトリに
.vs/、ビルド出力、個人用設定をコミットしない(.gitignoreを整備)。 - CI のビルド番号/コミット ID をアセンブリ情報や PDB に埋め込み、どのビルド物を実行しているか を可視化。
- Debug/Release の
csproj設定をテンプレート化して共通化(上記の PropertyGroup を流用)。 - Source Link を使うなら、同一コミットのソース と PDB が必ず一致するパイプラインにする。
- IL Weaving/難読化は Release 限定で有効化。Debug は完全オフ。
- Docker/WSL のパスマップをドキュメント化し、プロファイルをリポジトリに含める。
ケース別:よくある「External Source」→ 解決の道筋
| 症状 | 考えられる原因 | 確認ポイント | 具体的対処 |
|---|---|---|---|
| 未バインドのブレークポイント | PDB 不一致 / 古い DLL を実行 | モジュールのパスと PDB の署名 | .vs/bin/obj を削除 → 再ビルド → モジュールで手動 PDB 読み込み |
| 毎回 IL ビューが開く | 自動デコンパイルが有効 | デバッグ全般の設定 | Just My Code を有効化、 自動デコンパイルを無効化 |
| 行に止まらない/ズレる | Release 最適化/インライン化 | 構成が Release になっていないか | Debug で実行、JIT 最適化抑止にチェック |
| ソリューションでは再現しないが CI で再現 | Source Link/シンボルサーバーの混同 | どの PDB を読んでいるか | 一時的に外部シンボル無効化 → ローカル PDB で検証 |
| コンテナ/WSL でのみ再現 | ソースパスの不一致 | コンテナ内の実行パス | パスマップ定義、ボリュームの見直し |
| 単体テストでのみ再現 | テストホストの出力と本体 DLL の混在 | テストの実行パス | テストのビルド/実行ディレクトリを確認し、PDB の所在を揃える |
運用 Tips:トラブルを早期に発見・収束させるために
- プリビルドでキャッシュ掃除:ローカルの再現が読めない場合、プリビルドイベントに
$(ProjectDir)配下の一時生成物を掃除するスクリプトを短期的に組み込む。 - バージョン刻印:アセンブリにコミット ID/ビルド番号を刻印し、実行中のバイナリの正体 をログから即識別。
- ブレークポイントの「条件」整理:条件式やヒットカウントの設定ミスで止まらないケースも多い。まずは シンプルな無条件ブレークポイント で検証。
- 「外部コードのフレームを表示」設定:コールスタックの外部コードを展開しすぎると、視界に「External Source」が増える。切り分け時は非表示が有用。
PowerShell での一括クリーン(任意)
# プロジェクトルートで実行(VSは閉じる)
Get-ChildItem -Path . -Recurse -Force -Directory |
Where-Object { $_.Name -in @('.vs','bin','obj') } |
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
よくある質問(FAQ)
Q. Just My Code を有効にしても IL ビューが開きます。
A. 多くは PDB 不一致 が原因です。モジュールウィンドウで対象モジュールを右クリックし、「シンボルの読み込み」から 現在ビルドした PDB を手動指定してみてください。それで改善するなら、以後は外部シンボルを外す/ビルド出力と実行パスの一致を徹底します。
Q. PDB はあるのに「読み込まれない」と表示されます。
A. その PDB が 現在プロセスにロードされている DLL と一致していません。発行物(publish)や別ターゲットの出力を誤実行していないかを確認。CI の成果物をローカルで動かしている等も要注意です。
Q. Source Link を使いたいのですが、競合が怖いです。
A. まずはローカル PDB のみで正しく動く状態にしてから、Source Link を有効化し、同一コミット のソースを引けることを段階的に確認してください。動的生成コード(Razor/SourceGenerator)がある場合は生成物のパスや PDB 行マッピングもあわせて確認します。
Q. テストだけブレークしません。
A. テストホストが bin/Debug 以外の作業ディレクトリを用いることがあります。テスト出力の PDB に一致しているか、テストアダプタのバージョン差異がないかを点検してください。
再発防止のまとめ
- 最優先は「自分のコードのみ(Just My Code)」の有効化と「自動デコンパイル」の無効化。
- 設定変更後は
.vs/bin/objを削除して クリーン → 再ビルド。 - それでも解消しない場合は、PDB の読み込み状態をモジュールウィンドウで確認し、パスの一致・PDB の一致 を最短で保証する。
- Source Link/シンボルサーバーは一度外して切り分け、安定後に段階的に戻す。
- IL 変換や最適化、コンテナ・WSL のパス相違など、プロセス外要因 も疑う。
チェック用クイックリファレンス
- [デバッグ → 全般]で Just My Code = 有効、自動デコンパイル = 無効
- .vs/bin/obj を削除 してから再ビルド
- Debug 構成 & デバッグ情報 = フルまたはポータブル
- [モジュール]で シンボル = 読み込まれた になっているか
- 外部シンボル/Source Link は一時無効化して ローカル PDB で照合
- 別出力・別ターゲット・別プロセスを実行していないか確認
ポイントの総括:Visual Studio が「External Source」を表示するのは、VS が あなたの PDB とソースを同一のものと確信できていない サインです。Just My Code を軸に、キャッシュ整理 → PDB 一致の検証 → 外部要因の切り分け、の順で進めれば、ほとんどのケースは短時間で復旧します。

コメント