simotin13's message

simotin13といいます。記事の内容でご質問やご意見がありましたらお気軽にコメントしてください\^o^/

RXのアセンブラについてちょっとだけ調べてみた

先日「熱血アセンブラ入門を読んだ」という記事を書きましたが、

mcommit.hatenadiary.com

お正月休みで時間もあるので、個人的に気になっていたRXのアセンブラについて少し調べてみました。備忘録がてら調べた内容を挙げておきたいと思います。

調べた内容

開発環境はHEW(C/C++ compiler package for RX family V.1.02 Release 01)を使いアセンブラを見てみました。
解析してみたコードは「熱血アセンブラ入門」のsample.cのコードの一部と個人的に気になったコードの書き方などでどのようなアセンブラが出力されているのか確かめてみました。

HEWを使ってアセンブラを確認する方法は大きく2つあります。

1.アセンブラリストファイルを出力する。
"ビルド"→"RX Standard toolchain"→"コンパイラ"タブのカテゴリから"リスト"を選択し、"リスト出力"にチェックを入れます。
ビルドの成果物のディレクトリ(初期設定だとDebugやRelease)にファイル名.lstというファイルが出力されておりここにC言語のコードに対応するアセンブラが出力されます。
f:id:simotin13:20160105230152p:plain

2.デバッグ中に逆アセンブリを表示する
HEWをデバッグ動作させている最中に"表示"→"逆アセンブリ"から該当ファイルの逆アセンブリを確認できます。
f:id:simotin13:20160105230250j:plain

今回私はシミュレータによるデバッグにて2の方法で、逆アセンブリを確認してみました。
デバッグをしながらだと、レジスタやメモリ上の値を変えて実験できるので理解が深まります。
※1の方法も試してみましたが出力されるファイルに色々な情報が詰め込まれていて少し見づらかったということもあります。

予習

アセンブラを見てみる前にRXのアーキテクチャについてハードウェアマニュアルを読んで概要を押さえおきます。

- 汎用レジスタ16本(R0~R15)

32bitの汎用レジスタが16本あり、R0はスタックポインタとして使う。

- 制御レジスタ(9本)
  • 割り込みスタックポインタ(ISP
  • ユーザスタックポインタ(USP)
  • 割り込みテーブルレジスタ(INTB)
  • プログラムカウンタ(PC)
  • プロセッサステータスワード(PSW
  • バックアップPC(BPC)
  • バックアップPSW(BPSW)
  • 高速割り込みベクタレジスタ(FINTV)
  • 浮動小数点ステータスワード(FPSW)

ちょっと変わったところだとバックアップPCとバックアップPSWというのがある点でしょうか。
マニュアルによれば高速割り込み時にPCとPSWの値を退避するレジスタのようです。
確かに割り込み処理の処理速度を高めるためには退避用のレジスタがあると便利ですね。
また、ISPという割り込みスタックポインタという割り込み専用のスタックポインタもあります。
こちらは、リアルタイムOSを使う際にタスクと割り込みでスタック領域を分ける事でRAMの使用量を節約するのに活用するそうです。
割り込み処理内でローカル変数を多用するような場合は役に立ちそうですね。

ただし、こういった特殊なレジスタが多くなるとベンダー(ルネサス)以外がコンパイラを作るときの苦労が増えそうではあります。
GCCの開発者はこういうCPU向けにコンパイラを移植する際にどこら辺まで気にされているか気になる所です。
ルネサスコンパイラではオプションでこれらのレジスタを活用するかどうかを決めれるようになっているようですが。

C言語ソースコードアセンブラ出力結果

null return
void null()
{
  return;
}
02               RTS         

何もしないreturn文。
関数から戻る命令はRTS

0 を返す
int return_zero()
{
  return 0;
}
6601             MOV.L       #0H,R1
02               RTS         

0を返す関数。
R1レジスタに0を代入しているので戻り値の受け渡しにはR1レジスタを使うようです。
#0H,R1と並んでいるのでオペランドの指定はsrc→dstの並びになるようです。
また、RXはCISC系のCPUなので命令長は可変長ですね。

1 を返す
int return_one()
{
  return 1;
}
6611             MOV.L       #1H,R1
02               RTS         
int型サイズを返す
int return_int_size()
{
  return sizeof(int);
}
6641             MOV.L       #4H,R1
02               RTS         
ポインタ型サイズを返す
int return_pointer_size()
{
  return sizeof(int *);
}
6641             MOV.L       #4H,R1
02               RTS         

RXは32bitマイコンですのでint型・ポインタ型は4byteになるようです。

short型サイズを返す
int return_short_size()
{
  return sizeof(short);
}
6621             MOV.L       #2H,R1
02               RTS         

shortは2byte。

longを返す
int return_long_size()
{
  return sizeof(long);
}
6641             MOV.L       #4H,R1
02               RTS         

long型は4byte。

short型の値を返す
short return_short()
{
  return 0x7788;
}
FB1A8877         MOV.L       #7788H,R1
02               RTS         

short型の値もR1レジスタでそのまま返しています。
レジスタへの値設定はMOV.L命令で行われています。
Lはロング(32bit)サイズ、Wはワード(16bit)、Bはバイト(8bit)になりますので
short型の値を返す場合でもレジスタには32bitで書き込みを行っているようです。(上位ビットは0埋め)

ちなみに呼び出し側でどうなるか気になったので

main()
{
  volatile short hoge;
  hoge =return_short();
  if (hoge != 0) {
    while(1);
  }
}

というコードを書いてアセンブラを見てみました。
呼び出し側は

6040             SUB         #4H,R0
399FFF           BSR.W       _return_short 
D301             MOV.W       R1,[R0]
DC0E             MOV.W       [R0],R14
610E             CMP         #0H,R14
13               BEQ.S       0FFFF85E0H
2E00             BRA.B       0FFFF85DEH

というアセンブラになっておりました。
ローカル変数hogeへの書き込みはMOV.W R1,[R0]となっているため16bitで書き込まれているように見えます。
ところが、if文の比較は
CMP #0H,R14となっております。CMPはサイズを指定していないので32bitの比較になりそうです。

おやおや!このままだと16bitと32bitの比較になるぞ!コンパイラのバグか!?

と不思議に思い、気になってソフトウェアマニュアルのMOV命令の章を確かめてみました。
その結果分かったのですが、レジスタへの転送はMOV.W,MOV.Bであっても32bitに拡張されて転送されるとのことでした。


やれやれ、人騒がせなアセンブラですね。だったらレジスタへの書き込みには最初からMOV.Lにしておけよと思うところでした。
ちなみにwhile(1)の無限ループは

2E00             BRA.B       0FFFF85DEH

で表現されています。
0FFFF85DEHというのはこの行のアドレスなのでRXでは、

無限ループ=同一行へのジャンプ命令

によって実現しているようです。
実は、RXのプログラムカウンタは「次命令のアドレス」ではなくて「現在実行中の命令アドレス」を指しているそうです。
なのでプログラムカウンタにアドレスを直接設定してもあまり意味がないのでこのような形になるのかもしれません(あくまで推測ですが)


long型の値を返す
long return_long()
{
  return 0x778899aa;
}
FB12AA998877     MOV.L       #778899AAH,R1
02               RTS         

long型も4byteなのでint型と特に違いは無いようです。

マイナス値を返す場合
short return_short_upper()
{
  return 0xffee;
}
FB16EE           MOV.L       #-12H,R1
02               RTS

マイナスの値も定数値として指定できるようです。

足し算1
int add(int a, int b)
{
  return a + b;
}
4B21             ADD         R2,R1
02               RTS         

第1引数と第2引数の足し算です。
R1レジスタにR2レジスタの値を足してリターンしているようです。シンプルですね!

足し算2
int add3(int a, int b, int c)
{
  return a + b + c;
}
4B21             ADD         R2,R1
4B31             ADD         R3,R1
02               RTS         

3つの引数を足し算する場合も全てR1レジスタに足しこんでリターンしています。コンパイラの賢さを感じます!

インクリメント
int inc(int a)
{
  return ++a;
}
6211             ADD         #1H,R1
02               RTS         

ADD命令で単純に1加算しています。

感想

ざざっとアセンブラを見てみました。感想としては、

  • 仕事以外でアセンブラをまじまじと見つめることが無かったのでよい勉強になった。
  • 調べてみるとRXは高速化のためにいろいろと工夫がされているようだ
  • Hatenaのソースコード表示はアセンブラにも対応していて優秀だ。

といったところです。

今後やってみたい事

RXのアセンブラを見てみたので今度はARMのアセンブラも読んでみたいと思います。
amazonでARMのアセンブラに関する本も買ってしまいました。

ARMで学ぶ アセンブリ言語入門

ARMで学ぶ アセンブリ言語入門


そういえば、むかしリバースエンジニアリングの勉強用にと「はじめて読むPentiumマシン語入門」を買って読みましたが、これを機にもう一回読んでみたいと思います。

はじめて読むPentium マシン語入門編

はじめて読むPentium マシン語入門編

最近ではソフトウェアの抽象化が進む一方で、アセンブラ言語のような下層の技術についてはあまり流行とかが無いです。
ブログや雑誌などでは、「Javascript界隈」とかよく言われますが、「アセンブラ界隈」とか「組込界隈」とはあまり言わないですもんね。
※「組込界隈」って文字が既に一見さんお断り的な重々しい雰囲気をだしている気がする(笑)

個人的には最新の技術動向を押さえて何か作るのも嫌いではないですが、技術の動作原理や構造を理解することの方がなんとなく性に合っているようなので今年もそのスタンスを変えずに勉強していきたいと思います。