32ビット組み込みシステムにおけるGoランタイムバグ:netpollエイリアシングの深掘り
32ビットARMシステムでGoのnetpollに散発的なクラッシュが発生。ランタイムのポインタエイリアシングバグが原因で、数年にわたる報告の後に修正されました。

32ビットARM組み込みLinuxシステムでGoアプリケーションがクラッシュする問題は、アプリケーションのバグではなくランタイムのバグであることが判明しました。致命的なエラーruntime: netpoll: eventfd ready for something unexpectedは、Goのnetpollメカニズムにおける内部の前提条件を示していました。
根本原因:32ビットリトルエンディアンシステムでは、Goのタグ付きポインタロジックが32ビットアドレスとタグビットを8バイトのワードにパックします。epollイベントのev.Dataフィールドには、netpollEventFdへの生のポインタまたはpollDescへのタグ付きポインタが格納されます。ev.Dataをイベントfdのアドレスと比較する際、コードはuintptrにキャストしますが、32ビットでは4バイトしかないため、8バイトフィールドの下半分しかチェックしません。
このエイリアシングにより、fdseqタグ(再利用されたpollDescオブジェクトのカウンタ)が数百万に達すると、netpollEventFdのアドレスと一致する可能性があります。多くのpollDescオブジェクトを作成する長時間実行プログラムは、最終的にこれに遭遇し、netpollがソケットfdをイベントfdと誤認します。
このバグはGo 1.14(2020年)で導入され、2025年3月まで気付かれず、32ビットARMおよびi386システムからの報告のみがありました。2026年にマージされた修正では、イベントfdに対して生のポインタの代わりにタグ付きnil pollDescを格納し、エイリアシングを排除しました。
この話は、あまり一般的でないアーキテクチャでのテストの重要性を浮き彫りにしています。Goチーム自身の32ビットテスト不足が、このバグを何年も存続させた可能性があります。
Goランタイムは同じフィールドに生ポインタとタグ付きポインタの両方を格納し、32ビットリトルエンディアンシステムでは生ポインタがfdseqタグとエイリアスし、netpollがソケットfdをイベントfdと誤認します。
| ケース | 下位4バイト | 上位4バイト | 比較結果 |
|---|---|---|---|
| タグ付きpollDesc | fdseq(例:0x00000012) | *pollDesc(例:0x00123456) | 下位バイトがfdseqと一致し、イベントfdとは一致しない |
| 生のイベントfdポインタ | &netpollEventFd(例:0x00123456) | 0(未使用) | 下位バイトがイベントfdアドレスと一致 |
ディスカッション
0 件のコメント
最初のコメントを投稿しましょう。