CDPのアビス — 全4編
現在地:深界編
Executive Summary — 地上への帰還報告
- Playwright、Puppeteer、Seleniumを使わず、Node.jsの標準機能だけでEdgeとCDP通信する再現プログラムを作った。それでも同じプロファイルを再利用したダウンロードでEdgeはネイティブクラッシュした。
- Edge、プロファイル、HTML、5 MiBファイル、CDP命令、終了手順を同じにし、通信経路だけを
--remote-debugging-pipeから--remote-debugging-portへ変えた。- pipe経路では2回目または3回目の起動でクラッシュした。port経路は8/8成功し、全ファイルのサイズとSHA-256も一致した。
- 障害境界は「Edge + プロファイル再利用 + CDPダウンロード完了処理 + pipe」の組み合わせまで狭まった。ただし、Edge内部の原因コードまではまだ特定していない。
この層の呪い:案内役を置いていけば、便利な名前も例外処理も失われ、残るのは生のプロトコルだけになる。
この層の祝福:同じ命令を二つの縦穴へ流すと、片方だけに潜む異変が輪郭を持つ。
この層の遺物:外部依存を持たないraw CDP再現ハーネス。
深層碑文:「深界の光は答えではない。条件を一つだけ変えた対照実験が、帰還方向を照らす。」
現在深度:深界
浅層編では、古いプロファイル、headless、save_as() という目立つ容疑者を外した。
それでも、次の疑いは残っている。
Playwright独自の起動方法やCDP命令順が、Edgeをクラッシュさせているのではないか。
この疑いを調べるには、案内役のPlaywrightをここに置いていく必要がある。
ここから先で使わないもの
├─ Playwright
├─ Puppeteer
├─ Selenium
└─ 外部npmパッケージ
持っていくもの
├─ Node.jsの標準モジュール
├─ Edge
├─ 専用の新規プロファイル
├─ localhostのHTTPサーバー
└─ CDPのJSONメッセージ
上位ライブラリを外しても同じ事故へ到達するなら、少なくともPlaywright固有のPythonコードは必要条件ではない。
raw CDPという単独降下
CDPはChrome DevTools Protocolの略である。Chromium系ブラウザを検査・計測・操作するためのプロトコルで、Microsoft Edgeも互換性を持つ。
raw CDP再現では、ライブラリに操作を任せず、必要なメッセージを直接送る。
再現プログラムが行うことは次のとおりである。
- 同じPC自身を表すlocalhostにHTTPサーバーを起動する
- 5 MiBのテストファイルと、リンクを5つ持つHTMLを生成する
- テスト専用の空フォルダからEdgeプロファイルを作る
- CDPでページを開き、ダウンロードを許可する
- リンクを操作してファイルを取得する
- ファイルサイズとSHA-256を検証する
-
Browser.closeでEdgeを正常終了する - 同じプロファイルを使って次のEdgeを起動する
SHA-256は、ファイル内容から計算する「指紋」に相当する。サイズだけでなく指紋も一致させることで、成功と数えたファイルが途中で欠けていないことを確認する。
外部サイト、個人用Edgeプロファイル、拡張機能、認証状態には依存しない。
深界の二つの縦穴
CDPは同じ言語だが、Edgeへ届ける経路を選べる。
| 縦穴 | 実際の方式 | 通信のイメージ |
|---|---|---|
| pipe | --remote-debugging-pipe |
親子プロセス間の専用管を通す |
| port | --remote-debugging-port |
localhostのHTTP / WebSocket受付を通す |
どちらも最終的にはCDPメッセージをEdgeへ運ぶ。
だからこそ、ほかの条件を固定したまま経路だけを入れ替えると、障害境界を調べる強い対照実験になる。
pipe側へ降りる
再現側のコマンドは次の形である。
node repro.mjs --transport pipe --iterations 5 --download-behavior allow
観測環境では、最初のEdge起動とダウンロードは成功した。しかし、同じプロファイルを使った2回目、試行によっては3回目の起動中にEdgeが終了した。
run=1 transport=pipe result=ok
run=2 transport=pipe result=failed
exit_code=3221225477 (0xC0000005)
ダウンロード先には、5 MiBまで書き込まれた .crdownload が最終ファイル名へ変更されないまま残ることがあった。ファイル内容の転送後、ダウンロードを確定する付近で異変が起きている可能性を示す状況証拠である。
ただし、この一例だけで内部の正確な処理位置までは決められない。
port側へ降りる
次は、コマンドの一語だけを変更する。
node repro.mjs --transport port --iterations 8 --download-behavior allow
結果は8回すべて成功だった。
run=1 transport=port result=ok
run=2 transport=port result=ok
run=3 transport=port result=ok
run=4 transport=port result=ok
run=5 transport=port result=ok
run=6 transport=port result=ok
run=7 transport=port result=ok
run=8 transport=port result=ok
すべてのダウンロードは5 MiBで、生成時のSHA-256と一致した。
本当に縦穴以外は同じか
A/B比較では、次の条件を固定した。
- 同じEdge 152.0.4191.53の実行ファイル
- 同じユーザーデータディレクトリ
- 同じ5 MiBのHTTPレスポンス
- 同じHTMLとリンク操作
- 同じ
Browser.setDownloadBehavior - 同じCDPターゲット作成・接続手順
- 同じ
Browser.closeによる終了
独立変数はデバッグ通信のtransportだけである。
A: --remote-debugging-pipe
B: --remote-debugging-port=<一時的な空きポート>
同じ荷物、同じ機械、同じ指示で、縦穴だけが違う。
別の探窟機も降ろす
Edge固有の現象か、Chromium系全体の現象かを調べるため、Playwright同梱Chromiumでも対照を取った。
| 経路 | 観測結果 |
|---|---|
| Edge 152 + raw CDP pipe | 2~3回目の起動でネイティブクラッシュ |
| Edge 152 + raw CDP port | 8/8成功 |
| Edge 152をportで独立起動し、Playwrightから接続 | 2/2成功 |
| Playwright同梱Chromium 151 + persistent context / pipe | 5起動×5件、25/25成功 |
| 新規Edgeプロファイルの最初の1起動 | 5/5成功 |
Chromium 151とEdge 152は同じバージョンではないため、これだけでEdge 152の回帰とは言えない。それでも、「Chromium系なら必ず同じように落ちる」という説明には反する結果だった。
深界で描けた障害境界
ここまでの探索で残った組み合わせは次のとおりである。
Microsoft Edge
└─ 同じプロファイルを別のEdgeプロセスで再利用
└─ CDP制御下でダウンロードを完了
└─ remote-debugging-pipe経路
└─ ネイティブクラッシュ
一方、次の条件では同じ試行範囲で再現しなかった。
remote-debugging-portへ変更
Playwright同梱Chromiumへ変更
新規プロファイルの最初のEdge起動だけを使用
これは根本原因の特定ではない。事故が起きる地形の境界線を、再現可能な形で描いた段階である。
8/8成功という光を過信しない
port経路の8/8成功は、pipeとの比較として強い。しかし、8回成功しただけでport方式が永久に安全だと証明したことにはならない。
正確な表現は次のようになる。
観測した環境と試行範囲では、ほかの条件を固定してport経路へ変更するとクラッシュを再現しなかった。
深界で見つけた光を、太陽と取り違えないことが重要である。
次の層へ:記録石を拾う
通信経路まで狭めても、まだ最後の問いが残る。
Edgeはどのような異常で終了したのか。headed、headless、raw CDPは本当に同じ事故なのか。
最深部編では、Crashpadダンプを回収する。そこには、三つの経路すべてで同じ刻印が残っていた。
0xC0000005
msedge.dll
module offset 0x9D5C88B
near-null read
深度案内
- 地上編:
TargetClosedErrorは最深部の事故を説明しない - 浅層編:新品のプロファイルも、再利用すると様子が変わる
- 深界編:Playwrightを置いてraw CDPでpipe / port分岐へ降りる(本記事)
- 最深部編:Crashpadの記録石を読み、帰還路を選ぶ