CDPのアビス — 全4編
現在地:最深部編
Executive Summary — 地上への帰還報告
- Playwrightのheadless、headed、Playwright非依存のraw CDPという3経路から回収したクラッシュダンプは、すべてEdge 152の
msedge.dll内にある同じモジュールオフセット0x9D5C88Bを示した。- 例外は無効なメモリアドレスの読み取りを表す
0xC0000005で、読み取り先は0x18~0x21のnear-null領域だった。- ここまでの証拠で「Edge内部のネイティブクラッシュ」と「3経路が同じ事故へ到達した可能性」は強く示せる。一方、内部の原因コード、回帰範囲、悪用可能性までは断定できない。
- 観測環境で最も確度の高い帰還路は、Edgeを
--remote-debugging-port付きで独立起動し、Playwrightのconnect_over_cdp()で接続する方法だった。
この層の呪い:記録石は事故地点を示しても、原因となったソースコードまでは語らない。
この層の祝福:三つの降下経路が同じ刻印へ収束し、port側に帰還可能な道が残った。
この層の遺物:0xC0000005 / msedge.dll+0x9D5C88B / near-null read。
深層碑文:「最深部から持ち帰るべきものは、断定ではない。ほかの誰かが再現できる証拠である。」
現在深度:最深部
地上では TargetClosedError が見えた。
浅層では、プロファイル再利用、headless、save_as() を調べた。
深界ではPlaywrightを置き、raw CDPだけでpipeとportを比較した。
地上 TargetClosedError
↓
浅層 Profile / headed / headless / save_as
↓
深界 raw CDP / pipe / port
↓
最深部 Crashpad / 0xC0000005 / msedge.dll+0x9D5C88B
ここで回収するのは、異常終了したEdgeが残したCrashpadダンプである。
Crashpadは深層の記録石
EdgeやChromiumは、異常終了時の情報をCrashpadダンプとして残すことがある。
フライトレコーダーが事故直前の状態を調べる材料になるように、クラッシュダンプには例外コード、異常が発生した命令位置、読み込まれていたモジュールなどが含まれる。
今回、三つの独立した経路からダンプを採取した。
記録石A: Playwright + Edge / headless
記録石B: Playwright + Edge / headed
記録石C: Playwrightなし / raw CDP pipe + Edge
三つには、同じ刻印が残っていた。
Exception code: 0xC0000005
Access type: read
Module: msedge.dll 152.0.4191.53
Module offset: 0x9D5C88B
Invalid addresses: 0x18~0x21
0xC0000005 という刻印
Windowsの 0xC0000005 はAccess Violationである。アプリケーションが有効でないメモリアドレスを読み取り、書き込み、または実行しようとした場合に発生する。
今回の種別は read、つまり読み取りだった。対象アドレスは 0x18~0x21 と非常に小さい。
Microsoftの資料では、0x0~0x10000 の範囲はNULLポインターに関連する典型例として説明されている。今回の値は、そのnear-null領域に入る。
しかし、この情報だけから「Edge内部の特定変数がNULLだった」とまでは言えない。正確な原因特定には、対応するデバッグシンボル、完全なコールスタック、Edge側のソースコードを使った解析が必要になる。
絶対座標ではなく、地層の位置で比べる
ASLRという仕組みなどにより、msedge.dll がメモリ上のどこへ配置されるかは実行ごとに変わり得る。
そのため、絶対アドレスだけでなく、モジュール先頭からの相対位置で比較する。
module offset = exception address - module base
headless、headed、raw CDPで絶対アドレスが変わっても、モジュールオフセットは 0x9D5C88B で一致した。
別々の入口から降りた三つの探索隊が、同じ地層の同じ地点へ着いたことになる。これは、表面上は異なる実行経路が、同じネイティブ障害へ収束しているという強い証拠である。
最深部で言えること、言えないこと
深く潜るほど、断定できる範囲を厳密に分ける必要がある。
観測事実
- Edgeプロセスが
0xC0000005で終了した - 例外種別は無効なアドレスからの読み取りだった
- 例外命令はEdge 152.0.4191.53の
msedge.dll内にあった - 三つの独立経路でモジュールオフセットが一致した
- pipe経路では再現し、port経路では8/8成功した
強く支持される推論
-
TargetClosedErrorは原因ではなく、Edge終了後の上層症状である - 三つの実行経路は、同じEdgeネイティブ障害へ到達している
- 通信経路は再現条件を左右する重要な変数である
まだ分からないこと
- Edge内部のどのソースコードが根本原因か
- Edge 152で初めて発生した回帰か
- 別のOSビルドや別のPCでも同じ頻度で発生するか
- セキュリティ上の悪用可能性があるか
本件は安定性の不具合として扱う。証拠がないまま脆弱性やCVEを主張しない。
帰還路1:port側から戻る
深界のA/B比較で、もっとも確度が高かった回避策は次の構成である。
1. 専用プロファイルを指定する
2. Edgeを --remote-debugging-port 付きで独立起動する
3. localhostのCDPエンドポイントへPlaywrightから接続する
4. 既定のBrowserContextとPageを取得して操作する
Playwright側の接続部分は次の形になる。
browser = playwright.chromium.connect_over_cdp("http://127.0.0.1:9222")
context = browser.contexts[0]
page = context.pages[0]
実運用では、日常利用のプロファイルとは分けた専用プロファイルを用意し、デバッグポートを外部ネットワークへ公開しない。
Playwright公式は、connect_over_cdp() が通常のPlaywright Protocol接続より低い忠実度になる場合や、独立起動時の引数差によって一部機能へ影響が出る可能性を説明している。したがって、これは根本修正ではなく、観測範囲で有効だった帰還路である。
ほかの帰還路
要件によっては、別の経路も選べる。
| 帰還路 | 向いている状況 | 代償 |
|---|---|---|
Edge + port + connect_over_cdp()
|
Edge固有機能とログイン状態が必要 | 接続の忠実度や起動引数差を検証する必要がある |
| Playwright同梱Chromiumを使う | Edge固有の差異が不要 | 本番Edgeそのものの確認にはならない |
| 起動ごとに新しいプロファイルを作る | ログイン状態を保持しなくてよい | 認証と初期設定を毎回やり直す |
回避策は、原因を直したことを意味しない。通れない縦穴を避け、観測範囲で帰還できた経路を選んでいるだけである。
上昇負荷を逆向きに読む
最深部の記録を持って地上へ戻ると、最初の2行を違う意味で読める。
browser_disconnected reason=unexpected
Download.save_as: Target page, context or browser has been closed
これは「Playwrightが保存に失敗した」という一件だけではない。
msedge.dllでnear-null read
↓
Edgeが0xC0000005で終了
↓
CDP接続が切断
↓
Browser / Context / Pageが閉じる
↓
save_as() がTargetClosedErrorを返す
地上の警報を正しく解釈するには、深層から持ち帰った情報が必要だった。
地上に残した検証先
本文では探索過程に集中した。第三者が再現条件と公開記録を確認できるよう、検証先だけをここにまとめる。
原因コンポーネントと修正状況は、執筆時点では確定していない。クラッシュダンプそのものは、プロセスメモリやローカル情報を含む可能性があるため公開していない。
おわりに
地上で見えたのは TargetClosedError だった。
そこから、プロファイル、headless、保存API、起動引数、raw CDP、pipe / port、Crashpadへ順番に降りた。最後に見つかったのは、Edge内部の同じネイティブ命令位置と、port側に残された帰還路だった。
アビスを降りるために必要だったのは、推測の勢いではない。
- 一度に一つだけ条件を変える
- 失敗だけでなく成功も同じ精度で記録する
- 上位ライブラリを外して必要条件を調べる
- 事実、強い推論、未確認事項を分ける
- 回収した記録を、公開して安全な形へサニタイズする
上層のエラーは入口であって、答えではない。深く降りても断定を増やし過ぎず、持ち帰れる証拠だけを地上へ戻す。それが今回の探索で得た、最も再利用可能な知見だった。
深度案内
- 地上編:
TargetClosedErrorは最深部の事故を説明しない - 浅層編:新品のプロファイルも、再利用すると様子が変わる
- 深界編:Playwrightを置いてraw CDPでpipe / port分岐へ降りる
- 最深部編:Crashpadの記録石を読み、帰還路を選ぶ(本記事)