10/22/2019

[note] gccの内部仕様変更に伴う不具合に悩まされた話

今回はバグ、というかプログラムのミスの話です。それ自体は別段珍しくもないのですが、その原因がちょっと今まで遭遇したことのない類のものだったのと、それを引き起こしたミスが、かれこれ数十年プログラムを書いてきて、今更こんな・・・と驚いたという中々珍しい組み合わせだったので、戒め代わりに晒してみようかと思った次第です。

何かというと、画像関連の自作ツールで、十年以上前からちょくちょく修正や拡張を積み重ねて来て未だに現役のプログラムがあるんですが、そのLinux版について、速度の最適化をサボっていたところを改善しようと、久しぶりにリビルドしたところ、落ちるようになってしまったのです。

使用言語はCとC++の混在、主なライブラリはgtk3(glade含)とあとgnu関連色々、コンパイラは普通にgcc・・・ということで、特殊なものは使っていないのですが、この辺をそれなりに使っている人はよくご存知の通り、これらの環境は頻繁に仕様変更が入ります。gtkなんか数年で関数や構造ががんがん廃止されますから、その度に修正が必要になってとても面倒なのです。

で、まあ今回もその類の内部仕様の変更による不具合だろう、ということでデバッグを始めたのですが・・・何か妙なのです。どういう風に妙かというと、ステップ実行していると、とある関数で、その関数の実行が終わって最後まで到達すると、通常なら当然呼び出し先に戻る・・・筈なところ、そうならずに関数の途中に戻っていたのです。数十行巻き戻る感じですね。当然、色々と齟齬が出ますので、すぐにバイオレーションになって死亡、というわけです。

正直参りました。この手の挙動は、往々にしてオーバーランや初期化漏れ等のバグに起因する事が多く、その原因箇所が落ちている場所とは全く別の場所である事が殆どで、特定は困難極まるからです。とはいえ直さないわけにもいかず、仕方がないので、まずは絞り込み、ということで、その関数周辺の処理を大まかなブロックに分けて、ブロック別に有効/無効を色々と切り替えて実行を繰り返し、挙動に変化が見られるか観察しました。

結果は、ほとんど変化なし。巻き戻る場所が変わる位で、相変わらず落ちます。絞り込みも出来ない、というのは初めてで、非常に困惑させられたのですね。

埒が明かないので、最適化オプションを変えてみたらどうだろう、とMakefileをいじって試したところ、-O0、すなわち最適化なしだと落ちない事がわかりました。一歩前進です。ただ、完全に治るわけではなく、落ちないながらも無限ループは発生することもあります。こういう再現性が不安定なのは一番困るのですよね・・・ますます困惑は広がるばかり。

とはいえ、この事から、原因の一端が最適化にあるものと推測する事が出来ました。ので、オプションを-O1,-O2等に切り替えたりしつつ、ステップ実行を繰り返して、観察出来る変数の内容をトレースして絞り込みを試みました。が、ご存知の通り最適化が入ると変数(の大半)がデバッガからトレース出来なくなるんですよね。このため、なかなか上手くいきません。

ろくに進捗のないまま、試行錯誤を繰り返す事数時間、これは詰んだか・・・と思い始めた頃、ふと気づいたのです。何にかというと、問題の関数はint型の返り値を返すものなところ、その最後にreturn文がない事に、です。明らかにバグです。

私は基本、関数を書く時は、関数名とその型の次にまずreturn文を書くところから始めるのが常(voidの時は除く)なので、return文が無いというのは個人的には非常に珍しいミスです。あるいは何処かの修正時に誤って消してしまったのかもしれませんけれども、その辺りは不明です。

ともあれ、こんなミスが・・・と驚き、まさかreturn文の有無がそんな致命的な挙動に影響しないだろうし、今回の不具合とは関係ないだろうな、と思いつつも、せっかく気づいたんだし直しておくか、とreturn文を追加したのです。

すると、あにはからんや、件のループが発生しなくなっているではありませんか。どの最適化オプションを付けても問題なく、以前と同様に動作します。図らずも解決した事になります。

正直、嘘だろうと目を疑いました。しかし、色々テストをしてみても、完全に直っています。認めざるを得ませんでした。これが原因だったのです。

この修正で直った、ということは、逆に考えると、今回の不具合は、"現バージョンのgccでは、return文が無い場合、特に最適化をすると関数の終わりを関数内部のループの終わりと誤認する場合がある"、という事なのかと。

なんじゃそら、と思わずにはいられなかったわけです。

無論、返り値が返されない、という状況は正しいものではなく、というか紛うことなきバグで、その状況すなわちバグを作ったのは自分自身である以上、それに苦しめられるのも自業自得であることはわかっているのです。

ただ、常識的に考えれば、その不具合が影響するのは通常その返り値を参照する箇所であるのが普通だと思うし、今回のような不具合の事象からこの原因を連想ないし特定するのは極めて困難、というか不可能だと思うのですね。。。本件解決法は、本当に偶然ふと気づいただけですし、あの気付きがなければ、今でもありもしない、いかにもやらかしそうな類のバグを探して無駄に苦しんでいたのかと思うと、ぞっとします。

gccもgtkも、サイレント修正なんて日常茶飯事ですし、またこういう修正困難なバグに遭遇する可能性も否定できません。そうでなくとも容赦なく行われる改廃に、その際の種々のトラブルの予防を図る事も出来ず、その度に対応を迫られ続けるのだ、という悲しい事実を、改めて噛み締めさせられた一件でした。どうにかならないものなのでしょうか。少なくとも自分で作る部分のバグは0にするしかない?そうですね。。。とほほ。

10/07/2019

[note] タブレットのバッテリー交換

ご無沙汰してます、どころではないですね。一年以上ぶりの更新です。

久しぶりのネタは、先日、使用中のタブレットのうちの一台について、バッテリーが膨張して液晶が浮き上がる状態になってまして、その交換をした際の記録、という事になります。

本件、少し前(数ヶ月?)から、液晶の枠付近の色が黄色っぽくなっていまして、以前にスマホでバッテリーが膨張した際の変化と同じような、楕円形のシミのような変色も出ていたので、そのうち駄目になるんだろうな・・・と思っていたのが案の定、というわけです。膨張の原因はおそらく発熱で、本タブレットは元々性能の割に発熱があり、しかもプラスチック製の筐体で熱の籠もりやすい機種であったところ、本機でプレイしていたソシャゲのイベントがありまして、連続で長時間プレイして負荷が加わり続けたのがトドメになったものと思われます。最終的には、液晶が浮き上がって、横からLEDのバックライトの光が漏れるようになってしまっていました。

機種は、ASUS製Z380M。Zenpadの8インチ型ですね。廉価帯のモデルということで性能は低く、買い替えても1万数千円程度ではあるのですが、購入してから1年半程度で、バッテリー以外には問題もなさそうでしたし、OSもAndroidの6.0と現役、まだまだ使えるだろう、という事で修理する事にした次第なのです。

何はともあれまずは部品、という事でEbay経由で交換用のバッテリーを調達。ASUSのような大手製品は部品も入手が容易なのでこういう時は助かります。上記以前壊れたスマホは交換用部品がなく諦めたのです・・・とそれはともかく、型番は調べた限りてはC11P1510との事で、その互換品を購入しました。中国の深センの業者からの発送で、送料込み約2000円。2週間ほどで届きました。簡単な工具付です。(下図)


 一方、交換対象の方は下図。見事に膨らんでいます。カバーを開ける時、 弾け飛びそうな感じでした。カバーの明け方は省略。ネジを外して、爪を持ち上げつつパキパキと開けるだけです。なお、本体基盤の上側と下側をつなぐフレキケーブルが2本、バッテリーをまたぐ形で繋がれていたのですが、カバーを開けた際に膨張したバッテリーに引っ張られて外れてしまいました。外れたままだと動作確認も出来ないので、暫定的にバッテリーの下側を這わせています。

 

バッテリーの端子を外します。上に持ち上げてパコっと。


取り外したバッテリーユニット。おそらくはアルミ製のトレーに貼り付けられています。


バッテリーをトレーから剥がします。この時、粘着テープはかなり強力なものが使われているため剥がすのに苦労しますが、雑に扱ってバッテリーを損傷して発火したりしてはいけませんので、慎重に、ヘラ等を少しずつ粘着テープ部にコジ入れるようにして剥がすとよいでしょう。ちなみに私は、交換用バッテリーに付属してきたプラスチック製のピックを使いました。なお粘着テープは左右に二本取り付けられています。


交換用バッテリーと並べてみると、元が同じとはとても思えませんね。


トレーに交換用バッテリーを貼り付けます。なお、調達したのはC11P1510なのですが、取り外したバッテリーの型番はC11P1505で若干異なっていました。電圧・容量、サイズは同じなのですが、端子の位置が若干ズレています。交換品は少し外側に端子があるため、トレーに乗せる際に基盤端子に近づくように寄せて貼らないと、端子が届かなくなるので注意。下図で言うと左上ですね。寄せれば届きます。




フレキケーブルを元の通りに繋いで完了。フレキケーブルは端子後側のツメを上げ下げして噛み込むタイプですが、このタイプの例に漏れず華奢なので丁寧に。





ここで一旦動作確認。問題なく起動します。


安心したところで、カバーを閉じて、ネジ類を締めます。




無事修理完了、というわけで、今回はこれでおしまい。やれやれです。