P.120
SHとは
SuperHitachiの略
ルネサス製
左から右へ代入している
00fe1400 <_null>: fe1400: 00 0b rts fe1402: 00 09 nop 00fe1404 <_return_zero>: fe1404: 00 0b rts fe1406: e0 00 mov #0,r0
シャープ4とかシャープ2とかが即値
00fe140c <_return_int_size>: fe140c: 00 0b rts fe140e: e0 04 mov #4,r0 00fe1410 <_return_pointer_size>: fe1410: 00 0b rts fe1412: e0 04 mov #4,r0 00fe1414 <_return_short_size>: fe1414: 00 0b rts fe1416: e0 02 mov #2,r0 00fe1418 <_return_long_size>: fe1418: 00 0b rts fe141a: e0 04 mov #4,r0
そのサイズでの処理が一番適しているサイズが2ビットだったり4ビットだったりする
0x9001の01はPC相対で、大きなサイズの即値を格納している相対的な場所を指している
00fe141c <_return_short>: fe141c: 90 01 mov.w fe1422 <_return_short+0x6>,r0 ! 7788 fe141e: 00 0b rts fe1420: 00 09 nop fe1422: 77 88 add #-120,r7
では01ってどこから出ているのか。4+1*2
相対アドレス
補足
- PC(プログラムカウンタ)は,2つ先の命令を指している.
- mov.wで2バイトをPC相対でロードする際には,PC+オフセットで アドレスを計算している.(単純に加算するだけ)
mov.lで4バイトをPC相対でロードする際には,PCを4バイトアラインメント して,さらにオフセットを加算することでアドレスを計算している. (4バイトロードなので,アラインメントされる)
PCは1つ先の命令を指している。フェッチしたあと実行する。処理を早くするためにPCはさらに1つ先まで読んでおく。ただ、それが分岐命令だと先読みしたやつが無駄になってしまうので、遅延スロットでさらに実行しておく。遅延スロットは漢文でいうところのレ点。
05.01.06 4バイトの定数値は?
PC相対=その命令の位置からの相対値
long return_long() { return 0x778899aa; }
00fe1424 <_return_long>: fe1424: d0 01 mov.l fe142c <_return_long+0x8>,r0 ! 778899aa fe1426: 00 0b rts fe1428: 00 09 nop fe142a: 00 09 nop fe142c: 77 88 add #-120,r7 fe142e: 99 aa mov.w fe1586 <_call_many_args+0xc>,r9 ! 1f11
- w=word 2バイト(16ビット)
- l=long 4バイト(32ビット)
(ただし非常に環境依存)
アラインメント=切り上げ 2を4で切り上げってなると4 6を4で切り上げってなると8 割り算をして割り切れなかったら次の倍数にする
mov.lとmov.wで、アクセスの方法が違う
@kozossakai 読書会でSHの部分を読んでいます。PC相対についてサイトで訂正がありましたが、それでもまだ気になる部分があります。アラインメントは切り上げが一般的と思いますが、切り下げられているようです。詳細をまとめました。 http://t.co/UMjfAn2No8
— 七誌 (@7shi) 2014, 12月 16
- mov.wについてだけいえば、加算しているだけ
- mov.lについていえば、アラインメントと言いつつ切り下げをやっている
- 切り下げ 2を4で切り下げると0、6を4で切り下げると4(Excelでいうところのfloor)
- 切り下げアラインメント
一般的にアラインメントでは切り上げすることが多いように思いますが、a: d0 00と1e: d0 01は切り下げでアラインメントされています。というわけで「切り下げ」を明記した方が誤解が少ないように思います
(この解釈が正しいかどうかは著者のアンサーによる)
- 機械語で相対アドレスを見るとややこしいけど、SHはさらにややこしい
- 全部の命令に16ビット(2バイト)に詰め込まないといけないから
オフセット値の範囲外のアドレスはどうなるのか
相対アドレスはどこまで先まで指定できるのか
00-ff*4までなので、00-3fc
c→s→x→dでs(アセンブラ)を極大まで弄る
_return_short: .LFB9: .loc 1 37 0 .loc 1 39 0 mov.w .L9,r0 !, rts nop nop nop nop nop ...(ずっとnopをやってみる 256個nopを入れてみる) nop nop .align 1 .L9: .short 30600
- アセンブルの段階でpcrel too farエラーが出てくる
- アセンブラは裏で自動で生成されるので、ルールから外れると即終了する。
- 無茶なコードを書くとコンパイラが頑張ってアセンブラのルールに違反しない程度にアセンブルする(たぶんa=0みたいなのを大量に突っ込んだんだろうと推測)(アセンブラはここまでがんばってくれない)
1命令で代入できる場合もある
- -0x12は#-18(0x100を引く)
- FFFF=-1で、-0x12はFFEE 2の補数表現
付加するビット部分は元の値の最上位ビットで埋める(符号拡張)
- 正の値 付加されたバイトはすべてゼロで埋める(0x12→0x0012)
- 負の値 付加されたバイトはすべてffで埋める(0xee→0xffee)
short return_short_upper() { return 0xffee; } long return_long_upper() { return 0xffeeddcc; }
00fe1430 <_return_short_upper>: fe1430: 00 0b rts fe1432: e0 ee mov #-18,r0 00fe1434 <_return_long_upper>: fe1434: d0 01 mov.l fe143c <_return_long_upper+0x8>,r0 ! ffeeddcc fe1436: 00 0b rts fe1438: 00 09 nop fe143a: 00 09 nop fe143c: ff ee .word 0xffee fe143e: dd cc mov.l fe1770 <_erodata+0x174>,r13
オペランドとして指定できる即値は1バイト
符号計算を多用することで、命令サイズを削減するのが狙い。
なので、以下3つのワードは覚えておくとよいでしょう。他のCPUでも出てくるから。
- 相対アドレス指定
- PC相対
- 符号拡張
引数の扱いはどうなっているのか(P128)
// int aがr4 int return_arg1(int a) { return a; } // int aがr4 int bがr5 int return_arg2(int a, int b) { return b; }
00fe1440 <_return_arg1>: fe1440: 00 0b rts fe1442: 60 43 mov r4,r0 00fe1444 <_return_arg2>: fe1444: 00 0b rts fe1446: 60 53 mov r5,r0
2オペランド方式と3オペランド方式
int add(int a, int b) { return a + b; } int add3(int a, int b, int c) { return a + b + c; }
- rtsは遅延スロットでレ点
- rtsのあとに命令を1つ指定しないといけない
- sourceが左、destinationが右
- SHでは2つのレジスタしか指定できない
- 省バイト命令に向いている
- レジスタはr0-r15で16本ある
- 16本なので、ゆえに8ビット(1バイト)に2つのオペランドを格納できる
- 命令は16ビットなので、PowerPCやMIPSみたいに32本のレジスタにしちゃうと死ぬ
- どちらが優れているかではなく、どちらが向いているかが重要
// r0 = r4 + r5
r0 = r4
r0 += r5
00fe1448 <_add>: fe1448: 60 43 mov r4,r0 fe144a: 00 0b rts fe144c: 30 5c add r5,r0 00fe144e <_add3>: fe144e: 60 43 mov r4,r0 fe1450: 30 5c add r5,r0 fe1452: 00 0b rts fe1454: 30 6c add r6,r0
ARMを見てみよう
P130
- 携帯電話や組み込みの世界における覇権CPU
- ARMは、設計しかしていなくて、製造は他の会社にライセンスを売って任せている
- PowerPC<<<MIPS<<<SH<<<ARMという難易度
- アセンブラを勉強するにあたってはARMは外せないが、そこそこに難しいので、アーキの順番的にこの順番になった
ARMは条件コードを持っている
void null() { return; } int return_zero() { return 0; } int return_one() { return 1; }
00fe1400 <null>: fe1400: e1a0f00e mov pc, lr 00fe1404 <return_zero>: fe1404: e3a00000 mov r0, #0 fe1408: e1a0f00e mov pc, lr 00fe140c <return_one>: fe140c: e3a00001 mov r0, #1 fe1410: e1a0f00e mov pc, lr
- ARMの命令は4バイト固定(MIPS PowerPCと同じ)
- すべての機械語コードにeという値がついている
- これを条件コードという
- ほぼすべての命令に対してこのようなコードがついているので、条件分岐を使わないようにできる
- バイナリを色分けすると水色の線が見えたりする
ARMはリトルエンディアン
アセンブラ上の機械語コードはe1a0f00eで、実行形式上の機械語コードは0ef0a0e1となっていて逆になっている
- ARMはリトルエンディアン(値を下位桁から見る)
- MIPS PowerPC SHはビッグエンディアン(値を上位桁から見る)
- バイエンディアンというのがある
- リトルエンディアンは機械から見て読みやすい
- ビッグエンディアンは人間から見て読みやすい
- 型のサイズが拡張される場合の親和性が高い
- リトルエンディアンが主流で、ビッグエンディアンのCPUは少ない
ARMのリターン命令
- バイトオーダーorエンディアンネス
- mov pc, lrがreturn命令っぽい
- 遅延スロットはない
- 右半分が即値っぽい
- sourceが右、destinationが左
- lrはリンクレジスタ pcがプログラムカウンタ
- プログラムカウンタはコードの実行アドレス
- リンクレジスタは関数の呼びもと
- ARMのリンクレジスタは汎用レジスタ
- MIPS PowerPC SHのリンクレジスタは汎用レジスタではない
00fe1400 <null>: fe1400: e1a0f00e mov pc, lr 00fe1404 <return_zero>: fe1404: e3a00000 mov r0, #0 fe1408: e1a0f00e mov pc, lr 00fe140c <return_one>: fe140c: e3a00001 mov r0, #1 fe1410: e1a0f00e mov pc, lr