コードカバレッジを測る

評価計画を立てる作業を進めているが、その一環でコードカバレッジの測定をしていた。

評価を行うとどれぐらいコードが網羅できるかという話だから、

コードカバレッジの測定は計画時にしておく方がいいわけですよね。

他の工程との兼ね合いである程度時間が取れるからという側面もあるが。


コードカバレッジ測定用のツールはあるのだが、それでも容易とは言えないんだよな。

評価に使用する予定の設定情報を入れて動かしたり、

評価に使用する補助ツールで評価予定の項目を流したりして、そのときのコードカバレッジを見た。

思ったより網羅度は高かったが、やはりいろいろ通らないところはある。


ただ、ここは通らんだろうなというコードも多かった。

APIが成功コードを返さなかった場合はエラー処理に入るというコードを書いたが、

このAPIが失敗することはおおよそ考えられんだろうというのもある。

そういうのは「通過禁止」とセットすると計算から除外する機能があった。

「通過禁止」というのは良い言葉ですね。

コード上存在するが、このコードを通るようなことはあってはいけないと。

ある関数はそんなパスが10個以上存在していて驚いてしまった。


その上で通っていないパスをいろいろ検討していく。

このパスは○○のエラー処理を検証するから通るとか机上検討で埋めていく。

このパターンの評価は抜けていたなと気づけば評価項目に書く。

そのパターンの設定を加えたり、補助ツールの評価項目一覧に加えたり。

すると、それをきっかけにして見つかるバグってのもあるんですね。

絶対に通るはずだと思っていたパスが通らないので、他の部分の記述ミスを発見したり。

ある種の設定を50個以上並べると動作がおかしくなるバグがあって、

50個以上並べた場合にある操作を行った時だけ通るコードに記述ミスがあったと。

まさにデバッグ時のコードカバレッジが足りてなかったわけですね。

現実問題としてこんな個数並ぶことがあるのかはよくわからないけど。

しかし評価項目に入ってないのは盲点だったので書き加えておいた。


あと、抜けが目立ったのがUARTのフレーミングエラーですね。

普通にやっていてもフレーミングエラーなんて起きないからな。

評価項目に加えないといけないが、実現方法は難しいなと。

本来より大幅に遅いボーレートで通信をすれば容易に発生させることはできるが、

その場合に所定の動作になっているかというのは案外難しい。

誤った通信データを送るというのは全体的に難しいですね。

そういうのは発生し得ないと「通過禁止」とするのはさすがにダメだろう。


ただ、思ったより最初から高いカバレッジが出たのはよかったですね。

全然カバレッジがないと、どうやって埋めるか骨が折れただろうから。

精査してみると「通過禁止」や机上検討で明らかなものが多く、

この条件が抜けだねとみてすぐにわかるものが多かった。

まぁこの辺はコツがわかっているからというのもあるんだろうが。

IT管理者向けマニュアルが届く

職場でノートPCのUSBポートが壊れて、レンタル業者から代替機が届いて、

データを動かして、あれこれ新規インストールしてとやっていた。

もうこのツールはいらないかなというのは省略しつつ、

新しいバージョンのツールがリリースされているなという気づきとか、

アップデートするとなんかうまく動かないというのもあったり……


そんな中で全社的に提供されているツールをインストールしようとすると、

なんかエラーが出てうまくインストール出来ない。

手順を再確認したが、やはりうまくいかないので問い合わせた。

すると、この資料をみながらやってねというのが送られてきたのだが……

どう見てもIT管理者向けのマニュアルなんだが?


トラブルシュートの記載に今回の事象が載っているのかと思ったがそうではない。

というわけで頭から順番に見てみると、インストール内容を定義するXMLファイルの記載が間違えていた。

サンプル通りに記載を直すと正しくインストール出来た。

というわけで一件落着……でいいのか?


というわけでこう直したらうまく行ったけど、インストールキットは直さないの?

と連絡すると、別の担当者からインストールキットが間違えていたから直すという回答。

どうも最近、配布を始めたばかりで間違えていることに気づいてなかったらしい。

といっても、元の設定でちゃんと検証していたのかなど、謎は多い。


なぜ僕にIT管理者向けのマニュアルが送られてきたのか?

正直なところよくわからないところもあるのだが、

どうも外注業者がいて、分担しながらITインフラのマネージメントをしていると。

で、この問い合わせフォームへの回答は社内のIT担当者も外注業者もできるようになっている。

ここには何らか分担があるのだろうと思う。

で、その外注業者が問い合わせの背景など理解せずに管理者向けのマニュアルを送ってしまったのだろうと。

本当は社内のIT管理者に送って、お前らでどうにかしろという話だったのかもしれない。

そういうアドバイザーがいるのはなるほどねとは思うんですけど。


というわけで解せない話だった。

こういうインストールキットのミスは時たま発生していて、それに巻き込まれた話は時々聞く。

複雑な設定を排して簡単に共通的な環境をインストール出来るようにという工夫だが、

ちょっとしたミスで標準環境と乖離したり、そもそも動かなかったり……

まぁだいたい初期には何か起きてるんだろうなと思いますね。

AWAでプレイリストを公開する

僕はAWAという音楽の定額配信サービスを使っている。

AWAを知ったのはLinksMateのカウントフリーに入っていたことから。

LinksMateはCygamesの子会社が経営しており、ゲームのカウントフリーオプションで知られる。

主にはCygemesを含むサイバーエージェント関連の会社が関与しているゲームだが、そうでないものもある。

で、実はゲームだけじゃないんですよ。ABEMAやdアニメストアといった動画配信もある。

そして音楽配信という観点ではAWAがあると。サイバーエージェントの子会社が経営している。


といってもLinksMateを使ってるわけではないんですけどね。

当初はGoogle Play決済で安いということで使い始めたわけだが、

現在はクレジットカード決済と差が付いたので、Google Play決済ではない。

定額配信サービスの中ではちょっとマイナーな方かもしれないが、

基本的なところはあまり変わらないので、これはこれでよいのではないか。


そんなAWAで「THE IDOLM@STER M@STERS OF IDOL WORLD 2025」でセットリストのプレイリストを作っていた。

こういうの公式で出てくるのもあるんだけど……

でもAWAで作ってるのはあまり見ないか。(バンドリはAWAでは作ってない)

自分用に作るわけだが、せっかくなら他の人も使えるように公開しようとするのだが、

AWAのプレイリスト公開には大きな制約があるのである。

それは必ず8曲でなければならないということである。


公式アカウントで作るプレイリストはその限りではないんだけどね。

というわけでまず、自分用に全部並べたプレイリストを作る。

公式で公開されているセットリストを見ながら検索して投入していく。

Day1は57曲中52曲、Day2は60曲中53曲が入った。

8曲以上あるプレイリストを公開すると頭8曲分だけ共有されるので、

以降を8曲ずつ別のプレイリストにコピーしていく。

すると7つのプレイリストになる。最後は適当な曲で埋めておく。

これでとりあえず全曲を共有することが出来た。


これをどう使うかは各自にお任せということにはなるのだが、

オススメの使い方は自分のプレイリストにコピーすることである。

AWAでは公開されているプレイリストを自分のプレイリストにそのまま追加することができる。

すなわち自分でMoIW2025 Day1とかプレイリストを作って、

1番目から7番目まで順番に「プレイリストに追加」で加えていく。

すると最初から最後まで1本になったプレイリストが完成する。

最後に長さ調整のために埋めた曲は消しておけば完璧である。


ところで実際の楽曲とプレイリストに追加できた曲数には乖離があるが、

これはいずれも765PRO ALLSTARSの楽曲である。

シンデレラガールズはゲームに入っている曲は少なくともショートサイズで登録されているので、

これはなんやかんやいっても全部見つかっている。

765はアニメで使われた一部の楽曲を除いてさっぱり登録されていない。

以前、こんな話を書いたのだが、実際には放置状態である。

なお、日本コロムビアが発売元なのはアイドルマスターの無印もそうだが、

こちらは圧倒的に古い楽曲が多いため、定額配信を出し渋る理由は薄いと思う。

累積で聞かれることが定額配信のメリットとなると書いたことがあてはまる。

(日本コロムビアはCDを売りたい)

シンデレラガールズは来年春頃に定額配信をフルサイズ化する話が出ている。

これはデレステ更新停止に伴う対応の一環のようである。

シンデレラガールズの更新停止

シンデレラガールズに新規に触れる人のためにデレステと定額配信を使いたいと。

その理屈でいえば765なんてもうとうに終わってるやろとなるんですけどね。

現実はほとんどないという状態である。ダウンロード販売では大概買えるだろうけど、調べてはない。

何比較してもfalseになるNaN

評価計画を立てているときに、浮動小数点演算の結果が無限大になったときの処理も確認しないとな。

というところでIEEE754形式の浮動小数点数の資料を見たら、

NaNというのもあることを思いだし、これがけっこうなくせ者だった。


特殊な浮動小数点数として +0, –0, +∞, –∞とNaNがある。

+0と-0はどちらも0なのだが、符号ビットの概念が存在する。

通常は+0を使うのだが、計算によっては-0が生じることがある。

とはいえ大概の場合、意識する必要はない。

+∞, -∞はゼロ除算で生じることが比較的多い。

1.0/0.0=+∞, -1.0/0.0=-∞ といった具合である。

数学的には正しいとは言えないけど、概念的にはわかりますよね。


NaNは “Not a Number”の略で、漢字で書けば「非数」ってことになる。

なんのこっちゃという話だが、何らか未定義の値が出てくるとこうなる。

ゼロ除算でも 0.0/0.0 の場合は±∞ではなく NaN となる。

∞が絡む演算では 0.0×(±∞) や ∞-∞ は NaNとなる。

また、虚数が生じる演算 sqrt(-1.0) のような計算も NaN となるそう。

NaNが絡む演算も通常はNaN になるので 0.0/0.0+1.0 も NaN+1.0=NaNとなる。


浮動小数点数をスケーリング・整数化して表示する処理がある。

ここに +∞, –∞とNaN をそれぞれぶち込んでみた。

+∞, -∞は上限・下限に貼り付くような動作で、これはこれでよい。

しかしNaNは0になってしまった。一体なぜか。

float fval = roundf(forg * fscale);
if(fval < (float)SHRT_MIN){
     shortval = (short)SHRT_MIN;  //(1)
}else if(fval > (float)SHRT_MAX){
     shortval = (short)SHRT_MAX;  //(2)
}else{
     shortval = (short)fval;      //(3)
}

まず、forg×fscaleを四捨五入で整数化した値をfvalとして求める。

この値が -32768(SHRT_MIN)~32767(SHRT_MAX) であればそのままshortvalに代入する。

-32768未満であれば-32768、32767超であれば 32767 に丸める。

この処理で forg=-∞, fscaleが適当な正の数だと fval=-∞ となる。

-∞<SHRT_MIN なので(1)に分岐し、-32768 となり下限に貼り付く。

forg=+∞もfval=+∞となれば +∞>SHRT_MAX なので(2)に分岐し 32767 に貼り付く。


問題は forg=NaNで、fval=NaNとなった場合である。

実はNaNと他の数の比較はすべてfalseとなるルールがあるそうで、

NaN<0 も NaN>0 も NaN==NaN すらもfalseとなるのである。

すなわち、上記の(1)にも(2)にも該当せず、

あたかも -32768~32767 の範囲にあるかのように(3)に入る。

こうなったNaNをどのように整数変換するかは処理系依存だが、ARMでは0となるようだ。


ところでここまでの処理で例外とか発生しないの? と気になった人もいるかもしれない。

ARMではゼロ除算など不正な演算をしても例外にならないのが通常である。

レジスタ設定により例外を発生させることもできるみたいだけど。

ARMでは整数演算で0除算をすると0になるというルールを持っており、

100/0 のような演算をうっかりやってしまうと 0になるんですね。

x86はゼロ除算で例外が発生するのだが、必ずしもそうではないと。


何を比較してもfalseになるのが功を奏したものもあって、

a<bの場合に動作する処理を if(a<b){ … }else{ … } と書くと、aかbがNaNだとelse側に入ることになる。

数値比較してfalse側にフォールバック的な動作があればよいのだが、

逆に範囲外の処理をtrue側に書くと、ひっかからないということが起こる。

とりあえずNaNを何か範囲外相当の処理に落とし込みたいわけである。

というわけで考えたのはこういう書き方である。

if(!(fval >= (float)SHRT_MIN)){

「SHRT_MIN未満」という書き方を、あえて「SHRT_MIN以上でなければ」としている。

NaN以外の数に対する動作は何ら変わらない。

NaN >= SHRT_MIN は falseになるので、これを反転するとtrue になる。

これにより従来の書き方では分岐しなかった(1)の処理に入り、

fval=NaNは便宜上 -32768 に変換されることになる。

いかにも異常な数字であることが気づける可能性が高まる。


実際のところ +∞, –∞, NaN なんて発生させちゃいけないんですけどね。

ただ、設定次第で発生する可能性は否定できないので、

その場合でもなんらか説明ができる動作にしておくべきという話ですね。

比較順序によってはあまり問題にならないんですけどね。

ただ、意識して作らないとなかなかうまくいかないだろうなとは思った。

TeraTermでバイナリデータを受信する

マイコンの処理時間の測定をして、処理能力の見積もりをしていた。

処理量が多いと勝手に遅くなっていくという構造で、

一体どのぐらいあると遅くなるのかというのが見えてなかった。

おそらくほとんどの場合は最速ペースで動けそうなのだが、

遅くなるケースもちゃんと評価しないといけないのでね。


で、取得したデータを吐き出す方法をどうするかという話があって、

UARTで‘0’を送ると、測定データをバイナリで吐き出すようにした。

バイナリなのは文字列への変換が面倒だったからなのだが、

果たしてバイナリデータを保存するのは容易に出来るのか?

と、TeraTermのログ機能を見ると「バイナリ」という出力モードがあった。

バイナリでログを有効化して、’0’をタイプして、ログ終了して、

ログファイルをバイナリエディタで開くと所望のデータが得られた。

これでいいのか。


バイナリデータを送るのは面倒なのに、受信するのは簡単なんだな。

と思ったが、よく考えればファイルをドラッグアンドドロップすれば、

そのデータをバイナリで送信するということはできるんだった。

(この送信というのはYMODEMなど使わずに、単純にデータ列として送るということ)

だからあらかじめバイナリデータのファイルをあれこれ用意しておけば、

決まったバイナリデータをTeraTermで送るのは容易だったらしい。


後処理など考えればPythonなのかVBAなのか、送受信してデータ処理するプログラムを作るべきかも知れないが、

とりあえずデータを取ってくるという点ではこれで楽に出来た。

おかげでサクッと取って進めることが出来た。

ICEでメモリダンプするというのもあるけど、それだと実態に合わないのでは? という懸念もあったので。

静的解析がstaticを付けろと言う

プログラムのソースコードの静的解析というのがある。

コードを解析して、バグの原因になりそうな場所をいろいろ抽出してくれる。

打率は1割どころかもっと低い気がしますが。


これはバグだろうと多くの人に納得してもらえる例としては、

if(uintval<0){...}

と符号無しの変数が0未満であることを評価しているのだが、

この条件は常に不成立である。すなわちこの中のコードは実行されない。

その中に何か必要と思って書いている処理があっても実行されないので、

何かミスをしているということである。

変数の型を間違えているのか、条件を間違えているのかはわかりませんが。

そういうので再確認するべきポイントをいろいろ教えてくれるわけですね。


まぁそうはいっても馬鹿らしいものは多いのである。

その1つにファイル内でしか使われていない関数・グローバル変数にstaticが付いていないというのがある。

今までは「意図通り」とか書いてスルーすることは多かったが、

今回はその指摘に従っても良いかもなとファイル内に閉じるものにstaticに付けて回った。

そしたら次は1つの関数でしか使われていない変数があるという指摘があった。

まぁ関数内のstatic変数にするという方法はあるんですけどね。


C言語のstaticというのは2つの意味がある。それぞれ意味が異なる。

関数・グローバル変数に付ける場合はファイル内でしか利用できない関数・変数であることを示す。

どうしてファイル内に閉じる関数をstaticで宣言することがバグ対策として推奨されるのか?

例えばfoo.cでこのように宣言された関数があったとする。

void foofunc1(int x, int y){ ... }

かたやbar.cでこういうプロトタイプ宣言を書いたとする。

int foofunc1(char x, char y);

するとそれはそれで通ってしまい、名前が同じだとリンカで結合されてしまう。

当然、ここまでミスをしているとより強い指摘になるのだが、

潜在的なリスクを減らすにはプロトタイプ宣言を共通のヘッダファイルに書くべきであると。

ヘッダファイルのプロトタイプ宣言と実際の記述が間違えているとコンパイラが気づけるから。

あるいは関数名・変数名をミスタイプしていたようなケースならば、

それはヘッダファイルに書かれていないということで気づける。

一方でヘッダファイルに書かない場合はstaticとしてファイル内に閉じると明示するべきだと。


今まではどちらかというとヘッダファイルに列挙する方を選ぶことが多かったが、

内部に閉じるならstaticにするのも手と考えてこちらにした。

実は他のファイルでも必要でしたということで起こると困るが、

そうなる可能性はもう低いんじゃないかという読みである。

すると、次には1つの関数でしか使われていないという指摘が出てきたわけである。


記述ミスやバグが潜んでいる可能性はあるんでよね。

int fooval;
int foofunc2(int y){
  if(y > 0){

    fooval = y;
  }
  return fooval*200;
}

この書き方だとy≦0でfoovalが実行された場合は、すでにメモリ上にあるfoovalの値に200を掛けた値が返る。

前回の値を保存して計算することが目的ならこれでよいのだが、

実はy≦0の場合は、別の値を入れるべきところを書き忘れていたとすると気づけない。

もしfoovalが関数内の一時変数ならば、foovalが初期化されない場合があると指摘される。(なんならコンパイラが教えてくれる)


一時変数と間違えていませんかという指摘でもあるのだが、

一方で関数内のstatic変数として宣言することもできますねという指摘でもある。

int foofunc2(_Bool init){
  static int cnt;
  if(init){
    cnt = 0;
  }else{
    cnt++;
  }
  return cnt;
}

こういう書き方をするとcntという変数は次回関数実行時にも残る。

グローバル変数として宣言したのとほぼ同じ効果が得られる。

あくまでもローカル変数なので他の関数からは参照できないんですけどね。


ただ、これはデバッグに不都合だろうということで意図通りなら修正しなくてよいと考えた。

実際にはstaticなローカル変数もグローバル変数のように割り付けられているのだけど、

実際のところ、このタイプのstatic変数ってわかりにくい気がするんだよな。


と、この2つのstaticは効果が異なるわけですね。

修飾子なしのグローバル変数→static付きのグローバル変数→static付きのローカル変数

と同じようなものがだんだんスコープが狭くなっていくという話ではあるのだが。

このことについてWikibooksでは「文脈に応じて異なる意味と効果を持つ多目的な修飾子」と書かれている。

C言語/static (Wikibooks)

こういうのしばしばあるんですよね。

C++だとstaticメソッド・変数ってのもあるんですよね。

これはインスタンスではなくクラスに属するメソッド・変数っていうので意味が全く異なる。

これはさらに理解に苦しむ話である。


staticを付けたグローバル変数はファイル内でしか参照されないので、

ファイル内で代入だけして読み出されない変数についてコンパイラに指摘された。

もしstatic付きでなければ他の誰かがリードするかもしれないけど、

static付きなのでファイル内になければないですねということである。

これはデバッグ目的で代入しているものなのでその通りなのだが。

そしたら最適化で消されちゃうのか? と思ったが、見た感じはメモリに格納しているようだった。

staticを付けることで最適化のされ方も少し変わっているように見えた。

一体どういうところに差があるのかよくわからないけど。

コンパイラの警告は __attribute__((unused)) とか付ければ回避できる。

同期パスキーの目的

SBI証券といえば不正アクセス問題で相当な被害を出して、

それ以後、ログインの追加認証が複雑化してめんどくさいなぁと思っていた。

複雑化はしたが本質的にメールアドレス認証であることは変わらず、

僕はそのメールアドレスを携帯電話のMMSにしているから、PCのメールアドレスを乗っ取っても無理だが、

メールも乗っ取られて被害が拡大するのはよくある話。本質的な解決には遠い。

という中でWebサイトのログインにもパスキーが導入され、早速設定した。


で、これをやったときに気づいたのですが、スマートフォンで設定したパスキーがWindowsでも使えて、

あれ? パスキーってデバイスに紐付くものではなかったのかと。

あまり考えていなかったのだが、Googleパスワードマネージャーにパスキーが格納されているようだ。

現在はChromeでは特別な設定をしない限りはそれが基本になっているよう。

このため同じGoogleアカウントを使用するAndroid端末・Chromeでは全て使えてしまうと。


パスキーについて生体認証でログインできる仕組みと思っている人もいるかもしれないが、

正確に言えばそれは正しくなくて、デバイスに登録された秘密鍵でログインする仕組みである。

ただ、それを利用する際に生体認証やPINコードを要求するのが通例であり、

結果として生体認証に見えていると。でも重要なのはそこではない。

この秘密鍵を格納された端末なのか外付けのセキュリティデバイスなのか、

これを奪われなければログインされないということが重要であると思っていた。

逆に物だけ奪えばログインできるんじゃないかと思うかも知れないが、そこは生体認証なりPINコードで対策していると。


でも、その秘密鍵がクラウド上にあっては意味がないのでは?

秘密鍵が格納された物を奪われなければログインできないはずだったのに、

秘密鍵をクラウドで保持しているなら、そこにログインできるアカウントを乗っ取ればことが済んでしまう。

うーん、どうしてだろうと考えたがよくわからない。


このクラウド上に保存する仕組みを同期パスキーと呼ぶそうである。

比較的新しい仕組みだが、2022年からAndroid同士・iOS同士ではこの方式をサポート、

2024年からはGoogleはAndroidとPCのChromeブラウザの間でもサポートするようになった。

冒頭書いたようなAndroidで設定して、Windowsで使うようなことができるようになったのは本当に最近だったと。

見ての通り、この同期パスキーはスマートフォン同士での導入が先行したが、

パスキーをデバイス固定にすると、端末変更のたびにパスキーを失うことになる。

これがかえって悪いんじゃないかという話があったようである。

端末を変える度にパスキーを再登録するのはかえって脆弱ではないかと。

Googleアカウント、Appleアカウントが乗っ取られるとそれこそ大変なわけで、そこは強固になっているだろう。

そのアカウントに紐付く形でパスキーを保持するのなら十分安全ではないか。

パスキーの秘密鍵は通常は本人であっても目視することはない。

フィッシングサイトでパスワードが奪われるような被害は考えにくい。


そんなわけで同期パスキーを各プラットフォーマーは推奨していると。

ただ、当然ですがこれはGoogleアカウントやAppleアカウントが強固であるのが前提になっている。

これが脆弱だと結局は1つ乗っ取られれば芋づる式にやられてしまう。

パスキーが広く普及するにつれて、そういう問題も起きそうですがどうでしょうね。

SBI証券ということについて言えば、取引パスワードや、出金時のメール認証というのはあるけど、

ログインパスワードと取引パスワードが同じで被害を受けたり、

メール認証ということはGmail乗っ取られたらやられる人はいるだろうと、

そういうところまで考えて強固化できている人がいるのかという話ですね。


とはいえ、これでログイン時の追加認証がなくなるのは楽である。

元々手間がかかる割に効果的とも思えないものでしたからね。

パスキーを使っておけばフィッシングサイトへの対策には十分というのはおそらく正しいとみられる。

そこが達成出来ればよいということにしたのだろう。

VFPレジスタの退避節約術

昨日、ARM Cortex-Mシリーズで割り込みからの復帰に使われるEXC_RETURNの話を書いた。

EXC_RETURNを使わないといけない

さらに面倒な話があって、それが浮動小数点数レジスタのことである。


今回使っているマイコンはCortex-M33なのだが、浮動小数点演算用のVFPが付いている。

ARMv7で言うところのM3, M4に対応するARMv8のプロセッサがM33で、

M3とM4はVFPを積んでいるかどうかがだいたいの差で、

M33のVFPはオプションだったはずだが、大概積んでいる印象はある。

今回は浮動小数点演算を行うシステムなのでVFPを活用している。

整数の汎用レジスタとは別にVFPには単精度だと16個の浮動小数点数レジスタとFPSCRという状態レジスタがある。


これらのレジスタは割り込み処理で退避する対象となっている。

Cortex-Mシリーズではレジスタの退避はハードウェア的に行われるが、

浮動小数点機能を使わなければ32bitレジスタ8個(R0~R3, R12, LR, PC, xPSR)で済むのに、

浮動小数点を使うと+17個ということで大変である。

ただ、ここをサボるための仕組みがあるんですね。


Cortex-M4(F) Lazy Stacking and Context Switching – Application Note 298

M4の話だが、M33も仕組みは全く同じである。

まず、CONTROLレジスタにFPCAというビットがある。

初期値は0だが、浮動小数点命令を使うと自動的に1にセットされる。

FPCA=0で割り込みが発生すると8レジスタのみの退避を行う。

FPCA=1で割り込みが発生すると8+17レジスタの退避を行う……わけではない。

FPCCRレジスタの設定によるのだが、初期値(LSPEN=1, ASPEN=1)の場合は、

スタックポインタに8+17レジスタ分の領域を確保して、基本の8レジスタだけの退避を行う。

その上でCONTROL.FPCA=0, FPCCR.LSPACT=1 に設定する。


割り込みハンドラに浮動小数点演算がないパターンでは、FPCA=0のまま、EXC_RETURNに到達する。

この場合は割り込みハンドラ中にVFPレジスタは一切変化しなかったので、

CONTROL.FPCA=1, FPCCR.LSPACT=0 に戻すだけで完了となる。

あとは基本レジスタの復帰を行って、スタックポインタを戻して割り込み元に戻る。

割り込み処理の多くは浮動小数点演算を含まないだろうから、このパターンが比較的多いはず。

では、浮動小数点演算があった場合はどうなるか。

浮動小数点演算を行おうとすると、FPCA=0→1への変化が生じるが、

このときにFPCCR.LSPACT=1がセットされていたら、この時点でVFPレジスタの退避が行われる。

このようにレジスタ退避を後回しにする仕組みを”Lazy state preservation”と呼んでいる。

FPCA=1でEXC_RETURNに到達した場合は、VFPレジスタも復帰して元の処理に戻る。


で、この仕組みでもEXC_RETURNのアドレスが重要な意味を持っていて、

M33ではEXC_RETURNの4bit目が割り込み時点でのFPCAによって変わる。

割り込み時にFPCA=0の場合は4bit目は1、復帰時にスタックポインタを8レジスタ分だけ戻すことも表す。

割り込み時にFPCA=1の場合は4bit目は0、こちらはVFPレジスタ分もスタックポインタが進んでいて、

EXC_RETURN到達時のCONTROL.FPCAの値に応じて復帰要否が変わると。


で、昨日書いたCPU例外から再びmain関数に戻ってくる場合の話だが、

具体的にはEXC_RETURN=0xFFFFFFB8へジャンプすると、

Threadモード、Mainスタック、Non-Secure、特権モード、FPCA=0というリセット時と同様の状態になる。

ただ、このまま動かし続けるとそれはそれで問題があって、

それはFPCCR.LSPACT=1がセットされていると言うことである。

この状態で浮動小数点命令を実行すると誤ったレジスタ退避をしようとする。

ジャンプ後にFPCCRレジスタの値を明示的に設定する処理を入れれば問題は解消した。


割り込みハンドラでFPCA=0にセットする仕組みは多重割り込みにも効いて、

浮動小数点演算をしない割り込みハンドラ実行中に割り込みが発生した場合、

割り込みハンドラのレジスタ退避という観点では8レジスタの退避のみでよい。

レジスタ退避用のスタックサイズの削減にも寄与してるわけですね。

Mainスタックだけ使う前提で言えば、

  • main関数(VFP使用) スタック消費200byte+レジスタ退避104byte
  • 優先度2の割り込み(VFP不使用) スタック消費8byte+レジスタ退避32byte
  • 優先度1の割り込み(VFP使用) スタック消費40byte+レジスタ退避104byte
  • 優先度0の割り込み(VFP不使用) スタック消費8byte+レジスタ退避32byte
  • MPU例外ハンドラ スタック消費0byte

で、合計528byte消費する可能性があるわけですね。


全体的にはあまり深く考えなくても効率よく動くように考えられているが、

通常と異なる操作をするといろいろ踏んでしまうんですね。

この失敗がなければ気づかなかったことは多かったわけですけど。

EXC_RETURNを使わないといけない

最近はまたARM Cortex-Mシリーズのマイコンを使って仕事をしていて、

CPU例外発生後にその情報を引数で渡してmain関数に戻ってくるコードを書いたら、どうもうまく動かない。

main関数を再度呼び出す


ARM Cortex-Mシリーズでは割り込み発生時にハードウェア的にレジスタの退避が行われる。

なので割り込みハンドラでレジスタ退避のことを考えなくてよい。

それは知っていたのだが退避させたレジスタは復帰させなければならない。

この復帰処理に入るための仮想的なアドレス(EXC_RETURN)がリンクレジスタに入る。

リンクレジスタというのは関数の呼び出し元を表すレジスタで、

関数からreturnするというのはここにジャンプすることを指す。

で、例外ハンドラから出るときには何らかのEXC_RETURNにジャンプするべきようですね。


とはいえ例外ハンドラを出るときに例外発生場所に戻っては仕方ないわけで、

そこを変えたければスタックに積まれている例外発生元アドレスを変える必要がある。

sp+0x18が例外発生元アドレス、すなわち復帰先アドレスになっている。

退避されているレジスタの値を変えたければここを変えなきゃいけないんですね。

EXC_RETURNのアドレスもまた例外発生時の状態が保存されている。

復帰先のモードやレジスタ復帰方法がここで決まるんですね。

このあたりの値を変える処理はアセンブラで書かないと無理なので、

例外ハンドラから出るアセンブラのコードを書いて、例外ハンドラの最後から呼び出すようにした。


ただ、これはこれで問題があって例外ハンドラを出ると再度例外が発生するという。

例外ハンドラの中で例外フラグを消しておく必要があったようで、

CFSRレジスタをリードして同じ値をライトして、消去するとうまくいった。

このことからわかるが、例外ハンドラを実行中は例外が起きないんですね。

例外ハンドラからEXC_RETURNにジャンプすると再度例外が起こるようになると。

さらに言えば例外ハンドラの優先度は他の割り込みより高いので、例外ハンドラ実行中は他の割り込みが起きない。


ただ、他にも問題はあったんですよね。

それは浮動小数点レジスタの退避ですね。

普通はあまり意識しなくても効率よくやってくれるのだが、けっこう複雑な仕組みで……

このあたりもまた改めて紹介しようかなと。

これもEXC_RETURNが関係していると言うね。

ハイパーリンクで駆動するVBA

来週不在になる前にそこそこ動くようにしないといけないなと、

いろいろやっていたが、それなりに動く環境が完成して一安心?


今回、作り込んだ機能の1つにメンテナンスポートがある。

メンテナンスポートのシリアル通信でコマンドを送るとあれこれできるのだが……

問題はこのコマンドがテキストベースではないということである。

テキストデータの送受信ならTeraTermなど使えばよいわけだが、

バイナリデータの送受信となるとそれでは面倒である。

将来的には操作用のツールを作る話はあるが、暫定的に送受信できるツールが必要と、

これをExcelでVBAを使って作ることにした。


通常はワークシート上からVBAのプログラムを呼び出すにはボタンを置くが、

コマンドが多数ある中でボタンを多数並べるのは大変である。

それで調べるとハイパーリンクを使った方法があるという。

適当なセルに向けてハイパーリングを貼る。

このリンクはHyperlink関数で生成したものではダメである。

そしてWorksheet_FollowHyperlinkルーチンを定義する。

ハイパーリンクのクリックでマクロを実行する (グローウィン)

するとクリックすると、ハイパーリンクの情報とともにこのルーチンが呼び出される。


この方法のよいところはハイパーリンクの情報をもとに処理を振り分けられることである。

例えばハイパーリンクのセルのテキストにコマンドの入力・出力に使うセル名などの情報を書いておいて、

Worksheet_FollowHyperlinkルーチンではこれを解析して実行すると。

リンクテキストに情報を書き込むのは見た目がよくないが、

リンクのあるセルの右隣に必要な情報を書いておくとか、

機能のバリエーションが多い場合には有効な方策ではないか。


この方策によりメンテナンスポートで実行できる機能はほとんどExcelのワークシートから実行できるようになった。

これを使っていろいろ動作確認をしていたけど、やりたいことはだいたいできているように見える。

未実装機能はいくつか残っているし、ソースコードもいろいろ残骸が残って汚いが、

不在中の他のメンバーの作業には足りるぐらいの機能はあるんじゃないかね。