m68k用Linuxに実装されたstrcmp()のコードにバグ。12年間気がつかず 53
ストーリー by nagazou
使用頻度 部門より
使用頻度 部門より
Linuxに実装されているMotorola MC68000(m68k)アーキテクチャ向けに最適化された文字列比較に使用される「strcmp」関数の手書きのアセンブリコードが、2010年10月の実装時から2022年12月まで12年間の間、正常に動作していなかったことが判明した。Linus Torvalds氏はこの件に関して「常に壊れていた」と発言している(Phoronix、Linus Torvalds氏による説明)。
この問題についての詳細をツイートしているFadis氏の説明を引用すると、
Linuxのm68kサポートには2010年にアセンブリで書かれた高速なstrcmpの実装が追加された。しかしこの実装は2つの文字を8bit整数のまま減算して結果が0でなければそれを返り値とするという実装だった為、非ASCII文字を含む場合に正しくない結果を返す不具合があった
とのこと。
だめなのか? (スコア:2)
strcmp() の返値は、「文字列の大小」を反映した、負・ゼロ・正なんですが、そもそも、非Ascii文字列の「大小」なんて、統一的な定義は難しそうなので(あるのかな?)m68kの処理系では、これはバグではなく仕様である~にならないのかなぁ?
¶「だますのなら、最後までだまさなきゃね」/ 罵声に包まれて、君はほほえむ。
Re:だめなのか? (スコア:3)
納得した。
……と思ったけど、これだと(負の値が決して返らない)だと、ASCIIでもおかしくなるんではないかなと、ふと思った。
¶「だますのなら、最後までだまさなきゃね」/ 罵声に包まれて、君はほほえむ。
Re: (スコア:0)
当時のコンパイラはcharのデフォルトがsignedだったとかでしょう。
多言語対応でcharのデフォルトをunsignedにした処理系では正しく動かなくなる。
Re:だめなのか? (スコア:2)
> まあ、当事は、非Ascii な文字は事実上存在無かったということなのでしょうけど。
カーネル内には無かったのでしょうが、一般的には m68k の時代に ISO-8859-1(Latin-1) が無かったはずは無いかと。
C言語的に大小は定義されていないのかもしれませんが、人間的には ASCII の A (0x41=65) と Latin-1 アキュート・アクセント付きの A (0xC1) を比較したら、A < Á を期待するように思います。しかし 0xC1 を 193 でなく -63 と扱うと逆転する、と。
# ヌル文字依存の関数を使っているのは良いのか、という気もしました。
glibcの (スコア:1)
Re:glibcの (スコア:1)
カーネルは普通のlibcは使わんでしょうね
Re:glibcの (スコア:1)
Re: (スコア:0)
カーネル用途は求められるものが全然違うし開発してる人達も違うので無理矢理使い回す手間が割に合わんでしょ。
Re: (スコア:0)
コードをコピーしてたとか?
PPC用glibcでも (スコア:1)
2000年頃、PPC用のglibcでも
8ビット文字列を比較すると正しい結果を返さない
アセンブリコードで組まれたstrcmpの問題がありましたね
ソート順がおかしくなるってことね (スコア:0)
まあ68kでバリバリ非ASCII文字使うことないだろうからまあいいんじゃないっすかね。
Re:ソート順がおかしくなるってことね (スコア:2, すばらしい洞察)
strcmp()は2つの機能があって
- 文字列の一致不一致の判定
- 大小の比較
ができるけど、後者にバグがあったってことですね。
バグが出る条件は
- m68k
- 不一致の文字が非ASCIII文字の場合
で、 カーネル内部では大小の比較なんてめったに使わないし、そもそも m68k なんて誰も使ってない。
12年間も誰も気が付かないというか、12年間だれもつかってなかった、ってところでしょうね。
Re:ソート順がおかしくなるってことね (スコア:1)
親コメントが「ASCIII」になっていることも
ここで指摘しておかなければ誰も気づかないかもしれない
Re: (スコア:0)
どのみち、非ASCII文字がコード順でソートされても大した意味ないですからね。
JISなら多少は意味あったけどUnicode系だとね...
Re: (スコア:0)
おっと、漢数字の悪口はそこまでだ!
Re: (スコア:0)
複雑な挙動のテストケース漏れは仕方ないと思うけれど、こんな標準関数レベルですらテストコードが不十分だったんでしょうか。
このくらい基本的なテストコードなら、アセンブラでもアーキテクチャ依存でもなく共通で使いまわしができると思うのですが。
Re:ソート順がおかしくなるってことね (スコア:5, 参考になる)
元記事を読んでみた。
馬具は、アセンブラじゃなくて、asmを呼び出してるCのコードとの兼ね合い。
アセンブラは正しく実装されていて結果を符号付き8-bitで返している。なので、
負の値を返すには(カーネル内の問題となったstrcmpの戻り値の型がintだから)符号拡張しなければならんのだが、Cのコードは、このレジスタをcharで受けている。
charとだけ書くと、unsigned かsignedか決まっていなくて、処理系ごとにどちらかに決まる。
m68kで使っていたコンパイラはsigned扱いだったのね。なので、結果の8-bit目が1だったら、符号拡張されて、負の値がちゃんと返っていた。
そこに、コンパイルオプション-funsigned-charを追加したものだから、charはunsigned charと解釈され、char->ssize_tの変換で符号拡張しなくなって、負の値を返さなきゃならないときにも正の値が返るようになってバグが覚醒したと。
Re: (スコア:0)
見方によっちゃコードのバグではなくビルド設定のミスだな……
signed charとして扱う責任をビルド設定に持たすか各コードに持たすか
charの符号有無とかビルド設定切り替えるときは影響範囲確認せぇやって話だな
個人的には符号なしはbyte的な形で扱えば済むからcharは符号付きのが楽な気がしないでもない
でもis系関数相手だと符号無しのが楽よな……うーん
Re:ソート順がおかしくなるってことね (スコア:2)
このくらい基本的なテストコードなら、アセンブラでもアーキテクチャ依存でもなく共通で使いまわしができると思うのですが。
速度を稼ぐための「(m68k)アーキテクチャ向けに最適化された…アセンブリコード」から、標準のstrcmpを使うように修正した、という話ではないでしょうか。
int strcmp() に対して、明示型キャストなしで char res を return していたのが、to make 'char' be unsigned in the kernel across all architectures(commit 3bc753c06dd0: "kbuild: treat char as always unsigned"). でまずいことに気がつかれた、と読めます。
Re: (スコア:0)
実装がアセンブリだろうがCだとうが、strcmpをテストするコードはCで汎用的に書けるんだからx86もarmもm68kも同じテストコード使えるでしょ。
であれば、1回まともなテストコードを書いてテストしてたら真っ先に見つけられていたはずで
それすらできてなかったの?ってツッコミでしょ。
Re: (スコア:0)
後からなら何とでも言えるの典型
Re: (スコア:0)
標準関数にテストケースが無かったのかなんて、こんな珍事でもなきゃ話題にしようがないだろ
Re: (スコア:0)
それもあるが、「完全に思い込み」で書いているのが滲み出ている。読解力が無いのか、理解力が無いのか、時間が無いのか、知らんが、少し注意して読めば分かることを見逃している。
こういう方は、わかったつもりになって威張り散らす困った人になっていることに気づいてほしい。(昔の自分を見ているようで恥ずかしい。)
Re: (スコア:0)
#4387590です。
自分の書き方が拙くて勘違いさせてしまったようです。私の意図は、#4387690の方の指摘のとおりです。
でも、#4387599の方の技術的コメントが頂けたので恥を晒した分以上のリターンはありました。
#馬具という殆ど使わない単語の誤変換はどうして起きたんだろう?
Re: (スコア:0)
memcmpの方は大丈夫だったのかなあ
ASCII文字という先入観がない分、きちんとできたのかな
Re: (スコア:0)
str系の引数はconst char*だから引きずられてcharを使いがちだけど
mem系の引数はconst void*で必ず思考を強いられるからまずintかBYTEになるだろうね
--
¶「だますのなら、最後までだまさなきゃね」/ 罵声に包まれて、君はほほえむ。
ちょっと待って!! (スコア:0)
実装を変更する前に、テストプログラムをm68k以外と共通化すべきでは?実装の変更はその次で。
Mac用C/C++のcharがunsignedデフォルトなのはそういう (スコア:0)
まあLinux/m68kポートなんて生涯使うことないだろうからどうでもいいけど
もっといえばstrcmp自体最後に使ったのいつだった?って状態なのに、libcのstrcmpでさえなく、カーネル組み込みのstrcmpの方の話って…
どうでもよすぎて笑えてくる
Re: (スコア:0)
どちらかというと、よく気がついたなと思います。
#記事のリンク先の"Linusの説明"にある修正パッチが割り切っていて素敵。
どうすればいいの? (スコア:0)
#ifdef CP932のときはこういう風に1文字目2文字目判定して~って感じで言語を羅列しまくるのがいいの?
Re: (スコア:0)
https://srad.jp/comment/4387599 [srad.jp]
を読めばいい。
アセンブラのコードに間違いはないが
C側の符号符号判定の部分でバグが発生するとのこと。
てきとーに書く人多いよね (スコア:0)
Cのis-()系に(signed) charをそのまま渡している人は結構いそう。
# EOF以外の負の値を渡すのは未定義動作ですよ。
LinuxカーネルはCの規格は関係ないとは言え、GCCのマニュアルを読んでなかったことがあっただろ。
最近Cを学習してるんだけど (スコア:0)
カーネルってgcc使ってても自分自身で使う標準ライブラリは自分で用意するの?
Re: (スコア:0)
カーネルはすくなくともCのホスト環境では書かれていないので、string.h相当がなかったら自分で用意する必要がある。
ふつうにCと言ったらホスト環境でしょう。
Re: (スコア:0)
カーネルモードでユーザーモードのライブラリをそのまま使えないのは常識だが? WindowsもmsvcrtやUCRTを使えないから独自のRTL用意されているぞ。
たとえばユーザーモードでの例外やシグナルはカーネルがいい感じに処理してくれることを前提に書かれている。それをカーネルモードでそのまま使ったらどうなると思う?
Re: (スコア:0)
一番簡単な例で言うと標準Cライブラリのmalloc()やfree()はカーネルにお願いしてメモリを取ってきたり解放したりすることになる。
ゆえにカーネルは標準ライブラリは呼べない。
最近で現役の68kってあるの? (スコア:0)
派生したやつはこのライブラリ使ってるのかな
販売しているシステムorチップで現役の68kのものってありますかね
大分ARMとかに駆逐された感。
駆逐されたのは68kだけじゃないけど
Re: (スコア:0)
これ [zuiki.co.jp]は?
# 改めてLinuxカーネルをmakeすることはないかな
Re: (スコア:0)
ここんち、ARMのSoC屋さんだけど
「X68000 Z」ってARMでのエミュレーションじゃないの?
# それとも、単に「その68kじゃない」、といえばいいのかな
68KでLinuxが動作するとは信じられないのだが (スコア:0)
68Kといいつつ対象は68020以降とか?
Re: (スコア:0)
linuxでもMMU無しのサブセット版があって、それが動くはず。
使ったことないんだけど。
Re: (スコア:0)
まあ、x86といいつつ対象は80386以降だったUNIX互換OSもあったらしいし、ね。
Re: (スコア:0)
今は実用上、x86といったら普通は80386以降のことだと思うけど
68Kという呼び方は68000のことで、その他を指す用法はちょっと聞いたことがないなあ。
Re: (スコア:0)
最初のバージョンの話をしてるのになんで突然「今は」が出てくるの? 「今は」386どころか486のサポートもやめようとか話してるよ
Re: (スコア:0)
m68kは文字を素直に解釈するとMC68000のことだが、LinuxではMC68020+MC68851以上を指す。
同様にx86という呼び方は、i486が出た後にi386以上を一括りにするために生まれたが、
文字を素直に解釈するとi80186やi80286が含まれるので、正確な表現じゃない。
ということを、#4387831のコメントは言いたいんだと思う。
Re: (スコア:0)
M$は不安定というよりも
最近は余計なことし過ぎand/or中途半端で放置な気がする
Re: (スコア:0)
お久しぶりです。
M$で且つOSSならどう評価しますか?
Re: (スコア:0)
それはこっちじゃなくてアンチM$(笑)のOSS信者に聞くべき
Re: (スコア:0)
m68kが何か分かってなさそう
Re: (スコア:0)
え…x68k…