by shigemk2

当面は技術的なことしか書かない

熱血!アセンブラ入門 読書会(5) #hotasm

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で、アクセスの方法が違う

  • 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