このところ、本業が多忙で作業できてない私ことrin@です。
モチベ向上のために、Advent Calendarを利用して、 2020年12月〜2021年11月の思い出深い事件をまとめてみます。
お前の自分語りなんぞ興味ねーわ、 というツッコミが聞こえてきますが、 過疎ってるし、まあええやろ!
さすがに最終日は憚られたので、 24日までに間に合わせようとしたら意外と大変で、 リンクとか貼ってない手抜き版です。 詳細はnervのコミットログとか見てね: 本体, pkgsrc。 言い訳ばかりで面目ない!
ラズパイ2-3はearmv7{,hf}ebで、 ラズパイ3はaarch64ebで動くはずだったが、 mailbox, DMAC, early console等にlittle endian依存が残っていた。
ラズパイ0-1のearmv6{,hf}ebは、 Nのカーネルが動作した実績がなかった。
v6{,hf}ebのABIはv5{,hf}eb以前と互換性のあるBE32を想定していたが、 v7{,hf}eb以降と互換性のあるBE8に切り替えた。
N以外の人はfirmwareがlittle endianで動いてるマシンを わざわざbig-endian modeで使ったりしないので、 バイトオーダの切り替え方法はろくに解説されていない。 ARMv6でlittle-endian modeからBE8 modeに切り替えるには、 まずSCTLRのUビットを立ててから、 setend beする必要があった。
pmap_procwr()はnoopでも大抵のものは動いてしまうので、 まず壊れてる・まじめにテストされていないと思うのが吉。
基本的にはlibsaの枠組みで簡単にできた。 first-stage bootはself-relocationするのが少し面白かった。
IBM403の割り込みハンドラを書き直したら、 NetBSD 6以来、 長らく壊れていたのが動くようになった。 その後、wsfb(4) Xサーバなどもサポートした。
booke, ibm4xxにはoeaのMSR DEビットがない。 実はoeaよりはるかに高機能なHWデバッグ機構があるのだが、 どちらかと言えばfirmware/OS向けで、ptrace(2)には応用しづらい。 そこで、software breakpointを埋め込む方式に切り替えた。
WSDISPLAY_[GS]ETCMAP ioctl(2)が壊れていた。 Xサーバはハードウェアレジスタを直接いじるので、 動いているように見えていたのだった。 似たような理由でmlterm-fbが動かないドライバはまだありそう。
Quadra 800で驚異の実メモリ520MBが実現した。 680[46]0のpmapはしょぼいので、直さなきゃ。
libsaのネットワークコードの闇を見た。 mbufを使っていないので、 リニアバッファのポインタをデクリメントして、 ヘッダを追加していくのだった。 邪悪!
PCIスロットを2本搭載した可愛い評価ボード。 NがSoCをサポートしていたので、 PCIの割り込みルーティングとか書いてやるだけで動いた。
405関係のコードは整理したけど、まだ汚いなぁ。 DTB?
ページサイズが16KBなので(?)、nvme(4)が動かない。
UVM_KMF_NOWAITを指定せずにuvm_km_alloc(9)を呼んでいたため、 実メモリが足りないときにデッドロックしていた。
PMAP_CANFAILのときは、ENOMEMを返せばUVMが良きに計らってくれる。 そうでなければご愁傷様だが、仕方ない?
upstreamが4xxとbookeのサポートを壊していた。 誰も使ってない証拠。
F由来のコードはm_free(9)してからbus_dmamap_unload(9)する場合が多いが、 N/alpha等のbus_dmaでは逆にしないと動かなかった。
QEMUの中の人の質問に答えつつ、 細々とした修正をコミットする簡単なお仕事。 QEMU側の修正もコミットされたっぽい。
VFPを使うコードが正常に動くようになるなど。 ↓の問題との切り分けができて助かった。
GCCとLLVMではDWARFでのVFPレジスタの取り扱いに違いがあり、 前者がサポートできていなかった。 その結果、VFPを使うコードがC++の例外を投げた瞬間、 libunwind内の不整合でプロセスが死んでいた。 GCCのバージョンが上がる毎に、整数演算にVFPを使う最適化が進行して、 どいひー度合いが増していたのだった。
COMPAT_NETBSD32かつ作業ディレクトリがtmpfsという条件で、 lang/perl5がビルドできないという謎現象。 Nのino_tは64ビットで、 なおかつLP64カーネルのtmpfsでは上位32ビットがnon-zeroになる都合上、 miniperlにlong longサポートが入っていないと動かないところまで突き止めた。
perlの中の人たちと議論したところ、 inode numberがホストの整数型に収まらない場合は文字列として格納することで、 64-bit safeにしたつもりが、いくつかのモジュールで文字列から整数に再変換して 比較していたことが発覚、修正。
ARMv7以降はデフォルトでFPEを上げてこないので、誰も気づいていなかった。 これもどいひーなバグ。
実はaarch64でも壊れていたが、 little endianなのでたまたま動いているように見えた。 多様性、重要。
GCC10にNetBSD EABIサポートをupstreamしたときに、 ヘッダのインクルード順を間違えてエンバグしていた。
earmv[67]hfで複素数型を使うと落ちるという、 これまたどいひーなバグ修正をupstreamから取り込む。
MODULAR && !MULTIPROCESSORなカーネルで KERNEL_LOCK()はnoopになっていたが、 モジュール側では有効化されていて、 不整合が生じていた。
誰も!MULTIPROCESSORなカーネルなんか使っていないという証拠。
arm EABIではspは8バイト境界に整列している必要があるが、 compiler-rtの一部のルーチンが違反していた。
ARMv5teの{ld,st}rd命令は、 オペランドが8バイト境界に整列している必要があるため、 GCCがspのアラインメントを仮定した最適化をすると、 あちこちでalignment faultが発生することになる (↓のGCC/alphaの項を参照)。
ARMv6以降はunaligned accessが可能になったため、 誰も気づかなかった模様。
KERNEL_BASEのデフォルトが0xc0000000から0x80000000に 変更されたときに直すのを忘れていたため、 カーネルテキストとして+1GB余分にマップしようとして pmap_bootstrap()で落ちていた。
NetBSD 9.0のリリース直前、alphaで新しいjemallocが動かない問題が発覚。 がんばって二分法で探索して、最適化抑制で対処した。 んだけど、その後もいろんなコードが-O2で動かず、気持ち悪かった。
devel/gettext-toolsで発生した問題を詳しく解析したところ、 GCCはspが16バイト境界にアラインしているつもりで最適化していたが、 Nのカーネル・ユーザランドは8バイト境界のアラインメントしか保証していなかった。
alphaにはSystem V (ELF) ABIが存在しないので、 どちらが正しいと言うことでもないが、 パフォーマンスを考慮した結果、16バイト境界にアラインすることになった。
最近のGCCはこの手の最適化を積極的にやってくれる。 私の知る限り、Nではpowerpc, m68k, alpha, armv5が影響を被っている。
この時期、sh3のカーネルは相当不安定で、 full ATFすらおぼつかないような状況だった (現在は1個だけKASSERTを無効化しておけば、 DIAGNOSTICカーネルが問題なく動く)。
sh3ではコンテキストスイッチのときに、 SRレジスタを待避・復元するのだけれど、 SRには割り込みマスクが含まれている。 古いコードはこの部分まで復元していたので、 禁止していたはずの割り込みがいつの間にか許可されて、 めちゃくちゃになっていたのだった。
古いarmは割り込みまわりのコードが共通化されていないので、 けっこうカオスである。 XScale i80321には、 splraise(9)でIPLを下げることができるバグがあり、 これまた禁止したはずの割り込みが(以下同文)。
ラズパイのところで書いたとおり、 firmwareがlittle-endian modeで走り始めたarmプロセッサを、 big-endian modeに切り替える方法はほとんど黒魔術である。
玄箱PROのARM926EJ-Sの場合は、 SCTLRのBビットを立てる、 nop 3連発でプリフェッチバッファを空にする、 という操作をlittle endianでコーディングした後、 big endianでSCTLRを空読みするコードを書いてやれば、 後はbig-endian modeで動作する。 これでearmv5ebのテスト環境が手に入った。
ちなみに、HDL-Gのi80321では同じ方法が使えず、 big-endian modeで動かせていない。 どうしたものやら。
pmap_enter(9)がENOMEMを返すパスが間違っていたため、 pmap_page_protect(9)が無限ループに陥る場合があった。
どういうわけかaarch64ebとsparc64だけで失敗するテストがあり、 調べてみたところ、curses(3)が画面の書き換えを検出するために内部に 持っているhashの計算が間違っていた。 バグったアルゴリズムで計算した値がlittle endianと ILP32BEでたまたま一致していたので、LP64BEが壊れているように見えたのだった。 多様性、(以下同文)。
sh3マシンに負荷をかけると、 どういうわけかpc = 0番地でクラッシュする場合があった。 MDの関数ポインタがNULLになってる場所を一生懸命探したが見つからず、 あれこれコードを読んでいたら、例外ハンドラが間違っていて、 pcb_onfaultがNULLの場合もpcに上書きしていた。
Fedora 34というかglibc 2.33のrtldがfstatat64(2) のAT_EMPTY_PATHを使うために、 ダイナミックリンクライブラリを見つけられなくなっていた。 どうしてfstat(2)じゃダメなんですか、とかいろいろ意味不明だけど、 とりあえず雑サポートを追加。
PCMCIAやそのsubsetであるCFは、 ATAレジスタをI/O空間にマップできるほか、 古いPCMCIAの拡張メモリと同じスタイルでメモリマップすることもできる。 両者はpinoutが一部異なっていて、memory-mapped modeでは (原則として)割り込み線が使えず、ポーリング駆動が必要になる。
F, O, LはそもそもPCMCIA ATAのmemory-mapped modeなんて、 ハナからサポートしていないわけだが、 Jornadaやペルソナで使われているSoCである HD64461では、2つのPCMCIAスロットのうち一方は memory-mapped mode専用で、I/O-mappedはサポートしていないため、 Nでは必要になるケースもあるわけだ。
世の中の大半のATAコントローラは割り込み駆動なこともあり、 NCQサポートを追加したときにポーリング対応がエンバグしていた。
↑でも登場したHD64461というか、sh3コアのPCMCIA機能にはバグがあり、 16-bit wordの読み書きに続いて、byte単位の読み書きをすると、 誤って16-bit word全体がbyte-enabledになるという問題がある。 のだが、ウェブ上で探した限り、このバグについての文書が見つけられなかった。
↑のwdc(4)の問題を調べるために買った別冊インターフェース TECH I 14号のコラムで、このバグの詳細が解説されていた。 それに倣ってコードを修正したところ、 今まで動かなかったep(4) at pcmcia(4)が元気に動くようになった。
どういうわけかaarch64ebだけで失敗するtest caseがあり、 よくよく調べてみたところ、一種のstrict aliasing規約違反を発見した。 2種類の異なるポインタが指す実体を、 (ポインタを割り当てる前に) 構造体代入でコピーするのは、 少なくともGCCにとっては許しがたい行為のようだ。
MDの最適化やバイトオーダの都合上、 たまたまaarch64ebだけで顕在化していた。 多様性。
armのDDBはAPCSスタックフレームを前提としているので、 -mapcs-frameしないとback traceがとれないのだった。
今年はまあまあがんばったかな? そうでもないかな? 来年もよろしくお願いいたします。