読者です 読者をやめる 読者になる 読者になる

by shigemk2

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

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

勉強会

熱血!アセンブラ入門 読書会(7) - connpass

おさらい

エイリアスとは

PPCやMIPSはあまり命令の数がない。ので、他のやつで組み合わせでできるものは他のもので表現してしまおう

PowerPCでは

  • liはload immdiate
  • addiの特殊なケースがli
  • r0は値はゼロとして扱うので、ゼロに何を足してもそのままの値となる
  • li命令を別に作ると命令サイズを逼迫するのでaddiでどうにかしよう、という考え方
addi r3,r0,xxxxx
li r3, xxxx

MIPSでは

  • i=immidiate
  • v0 ゼロレジスタ v0 = 0 + xxxx
  • 足し算命令のひとつ特殊な場合をliとみなしている
  • 数字をレジスタとして代入する
addiu v0, zero, xxxx
li v0, xxxx

レジスタからレジスタへ

PowerPC

orの第2オペランドと第3オペランドが等しいとき

or r3,r4,r4
mr r3,r4

MIPS

  • adduは即値ではないとき
  • ゼロを足すだけだとあまり意味がないので、move扱いとする
addu v0,a0,zero
move v0,a0

特別な命令を増やさないで他の命令の一部にのっかる感じで。

6.5.1 nop命令もエイリアスになっている

MIPSでは逆アセンプルの際にエイリアスの変換をせずに元の命令をそのまま出力するオプションがあります(PowerPCにはそういうオプションがないかもしれない)

00fe1400 <null>:
  fe1400:   03e00008    jr  $31
  fe1404:   00000000    sll     $0,$0,0xO

00fe1408 <return_zero>:
  fe1408:   03e00008    jr  $31
  fe140c:   00001021    addu    $2,$0,$0

00fe1410 <return_one>:
  fe1410:   03e00008    jr  $31
  fe1414:   24020001    addu    $2,$0,1

たしかにmoveやliがadduになっている

比較

00fe1400 <null>:
  fe1400:   03e00008    jr  ra
  fe1404:   00000000    nop

00fe1408 <return_zero>:
  fe1408:   03e00008    jr  ra
  fe140c:   00001021    move    v0,zero

00fe1410 <return_one>:
  fe1410:   03e00008    jr  ra
  fe1414:   24020001    li  v0,1

6.5.2 sll命令とはなにか

この「sll」という命令は,いったい何なのでしょうか? ちょっと調べてみましょう.

fe1404: 00000000 sll $0,$0,0xO
fe1404: 00010000 sll $0,$1,0xO
fe1404: 00000100 sll $0,$0,0x4

f:id:shigemk2:20150128202041p:plain

6.5.3 sll命令はシフト演算を行う命令

実は sllはシフト命令で,即値で指定されたビット数ぶんシフト演算を行う。

sll $0,$0,0x0

事実上なにもしない。ゆえにnop これがnopの正体。

命令は32ビットしかないので、32ビット以上シフトすると中身が消滅する

6.5.4 PowerPCでは nopは ori命令になっている

PowerPCでは, nopの実体は何

ここでおさらい。c言語をコンパイルしてアセンブラsファイルを出して、sファイルをアセンブルしてオブジェクトファイルoを出し、オブジェクトファイルoをリンクして実行ファイルxを出し、xファイルを逆アセンブルしてdファイルを出す。

  1. コンパイル
  2. アセンブル
  3. リンク
  4. 逆アセンブル

sファイルはコンパイラが出力したおまじないがおおくて読みにくい

バイナリは仮のものと最終品の2つあるといってよい。

sファイルが作り直されると、他のファイルも作り直される。

f:id:shigemk2:20150128203021p:plain

途中でnopを仕込んでmakeしたやつの逆アセンブルファイル

f:id:shigemk2:20150128203418p:plain

nopとoriは似ている。

00fe143c <return_long>:
  fe143c:   3c 60 77 88     lis     r3,30600
  fe1440:   60 63 99 aa     ori     r3,r3,39338
  fe1444:   4e 80 00 20     blr

nopは先頭ノ守イトの「60」以外はオールゼロになっています. ということはnopの正体はオペランドがすべてゼロの ori命令で,「ori rO, rO, OxOOOO」のようなものになっていると推測できる

ori r0,r0,0
! r0 = r0 | 0
nop

こうしてみると、ori r0,r0,0は何もしていない=nopなのがわかる

あんまり深い意味はない。

6.6 まとめ

  • 固定長命令の RISCプロセッサではエイリアスにより命令数や回路規模を節約
  • エイリアスは CPUのドキュメントの命令一覧などには載っていない
  • その場合は別命令のエイリアスになっているかどうか探してみる

7 メモリ操作

コンビュータの目的は「データ処理」にある。=「CPUが直接扱える」=「機械語命令によって直接アクセスできる」

メモリ操作はよくわかっていない

7.1 PowerPCのメモリ操作

sample.cを読み進めると, load()/store()という関数があります

メモリからレジスタに値をコピーすることを一般にロード(load)、その逆にレジスタの値をメモリに書き込むことをストア(store)と呼ぶ。

とりあえずc言語を見てみよう。

メモリのp番目を呼び出すのがload メモリに直接値を書き込んだりメモリの値を呼び出したりできる。

// p番目のメモリの中身を呼び出す
int load(volatile int *p)
{
  return *p;
}
// p番目のメモリに値を書き込む
void store(volatile int *p)
{
  *p = 0xff;
}

PowerPC

  • lwzがロードの命令で、lがloadの意味なのでは
  • 0(r3)の0ってなんなの
00fe149c <load>:
  fe149c:   80 63 00 00     lwz     r3,0(r3) ! r3レジスタの指す先のメモリの値をロードして r3レジスタに格納する
  fe14a0:   4e 80 00 20     blr

00fe14a4 <store>:
  fe14a4:   38 00 00 ff     li      r0,255
  fe14a8:   90 03 00 00     stw     r0,0(r3) ! r3レジスタの指す先に Oxffが代入
  fe14ac:   4e 80 00 20     blr

lwzとstwで命令の向きが変わっている。stw r0,255みたく即値を直接アドレスに入れる命令はPowerPCにはないみたい。なのでliでr0に値を入れてから、r0の中身をr3に入れている

lwzとstwで命令の中身が大きく違う。

7.1.2 構造体のメンバへのアクセス

構造体へのメンバへのアクセス

// 中身は知らない
struct structure {
  int a;
  int b;
  int c;
};

// 受け取る引数は構造体へのポインタ(アドレス)
int member(struct structure *p)
{
  p->b = 1; // stw
  return p->c; // lwz
}

PowerPC

構造体のアセンブラはなくって、関数から呼び出されないと逆アセンブルされない。

00fe14c8 <member>:
  fe14c8:   38 00 00 01     li      r0,1
  fe14cc:   90 03 00 04     stw     r0,4(r3)
  fe14d0:   80 63 00 08     lwz     r3,8(r3)
  fe14d4:   4e 80 00 20     blr

「p→b」や「p->c」は構造体のメンバ。これらのメンバはすべてint型なので、「pー>b」は構造体の先頭から 4バイト、「p一>c」は 8バイトの場所にあるはず。

なので4バイトずつディスプレースメントされている。

オフセットを加算したアドレスに対して直接読み書き

0(r3)=(r3+0)
4(r3)=(r3+4)
8(r3)=(r3+8)

こういうふうに書けば、次のアドレスにアクセスできる。

7.1.3 アドレッシングモード

レジスタからアドレス値を得る方法(アドレッシングモード) P118

ここでは、メモリの指定の仕方みたいな意味合いで考えてみると良いかもしれない。

ディスプレースメント付きレジスタ間接 0(r3)みたいな書き方のこと。

なので、disp(r3) = (r3 + disp) みたいな書き方をする。

レジスタ間接 r3 = r3

インデックスつきレジスタ間接 r0(r3)

4r0(r3)みたいな指定ができるCPUがある。(r3+4r0) 命令で演算しなくてもレジスタ同士で演算してたりする。

PowerPCからすごい飛躍してるな。

  • w 4バイト
  • h 2バイト
  • b 1バイト

みたいなくくりが一般共通じゃなかったりする。

7.1.4 静的変数のアドレスの格納

プログラム自体に最初からある変数のことを静的変数という。で、このC言語はこの静的変数をいろいろとごにょごにょしている。

int static_value = 10;
long static_long = 0x12345678;

int *get_static_value_addr()
{
  return &static_value;
}

int get_static_value()
{
  return static_value;
}

void set_static_value(int a)
{
  static_value = a;
}

PowerPC

00fe14d8 <get_static_value_addr>:
  fe14d8:   3c 60 00 fe     lis     r3,254
  fe14dc:   38 63 18 00     addi    r3,r3,6144
  fe14e0:   4e 80 00 20     blr

00fe14e4 <get_static_value>:
  fe14e4:   3d 20 00 fe     lis     r9,254
  fe14e8:   80 69 18 00     lwz     r3,6144(r9)
  fe14ec:   4e 80 00 20     blr

00fe14f0 <set_static_value>:
  fe14f0:   3d 20 00 fe     lis     r9,254
  fe14f4:   90 69 18 00     stw     r3,6144(r9)
  fe14f8:   4e 80 00 20     blr

変数がどこにあるのか、っていう。

f:id:shigemk2:20150128211701p:plain

f:id:shigemk2:20150128211725p:plain

変数もどこかのアドレスに格納されている。

static_valueはOx00fe1800というアドレスに配置されているようだ。

0x00fe1800を探してみよう。

00fe14d8 <get_static_value_addr>:
  fe14d8:   3c 60 00 fe     lis     r3,254
  fe14dc:   38 63 18 00     addi    r3,r3,6144
  fe14e0:   4e 80 00 20     blr

大きな即値は複数回の命令に分けて格納している。ここではlisとaddi

(lisは0x00fe0000をr3に代入しているのであって、0x00feだけr3に入れているわけではない)

なので、r3 = 0x00fe0000 + 0x00001800 というイメージ

メモリがあってそこから出し入れしている。何番目のメモリを取り出しているか。

急に難しくなっているけど、安心してください。何度も説明があるらしいので。

7.1.5 静的変数へのアクセス

00fe14e4 <get_static_value>:
  fe14e4:   3d 20 00 fe     lis     r9,254
  fe14e8:   80 69 18 00     lwz     r3,6144(r9) ! ディスプレースメントつきレジスタ間接 6144=0x1800
  fe14ec:   4e 80 00 20     blr

「メモリ上のアドレス Ox00fe1800に置いてある値を r3レジスタにロードして返る」 r3にたいしてr9の値を読みこめばいい。

  1. r9=0x00fe0000
  2. r3=(r9+0x1800)

ディスプレースメント付きレジスタ間接は命令数の削減を目的としている。

別解

f:id:shigemk2:20150128213040p:plain

でもこの方法だと3命令必要になるので、省メモリ化を図っている。

書き込み。lisしてstwしてるので、ぶっちゃけlwzと同じ。

00fe14f0 <set_static_value>:
  fe14f0:   3d 20 00 fe     lis     r9,254
  fe14f4:   90 69 18 00     stw     r3,6144(r9)
  fe14f8:   4e 80 00 20     blr

変数はメモリにあるのがポイント。

7.2 MIPSでのメモリ操作

復習をかねたMIPS

7.2.1 口ード/ストアはPowerPCと似ている

MIPS

とりあえずレ点の遅延スロットは無視したらいい。くらいで、命令は割と似ている。が、静的変数の扱い方はちょっと違う。

00fe14ec <get_static_value_addr>:
  fe14ec:   03e00008    jr  ra
  fe14f0:   2782fff4    addiu   v0,gp,-12

00fe14f4 <get_static_value>:
  fe14f4:   8f82fff4    lw  v0,-12(gp)
  fe14f8:   03e00008    jr  ra
  fe14fc:   00000000    nop

00fe1500 <set_static_value>:
  fe1500:   03e00008    jr  ra
  fe1504:   af84fff4    sw  a0,-12(gp)

PowerPC

00fe14d8 <get_static_value_addr>:
  fe14d8:   3c 60 00 fe     lis     r3,254
  fe14dc:   38 63 18 00     addi    r3,r3,6144
  fe14e0:   4e 80 00 20     blr

00fe14e4 <get_static_value>:
  fe14e4:   3d 20 00 fe     lis     r9,254
  fe14e8:   80 69 18 00     lwz     r3,6144(r9)
  fe14ec:   4e 80 00 20     blr

00fe14f0 <set_static_value>:
  fe14f0:   3d 20 00 fe     lis     r9,254
  fe14f4:   90 69 18 00     stw     r3,6144(r9)
  fe14f8:   4e 80 00 20     blr

MIPSのほうが読みやすい。

7.2.2 グローバルポインタ

ここは難しい。

gp=グローバルポインタで、読み書きコストを下げるワザ。

00fe14f4 <get_static_value>:
  fe14f4:   8f82fff4    lw  v0,-12(gp)
  fe14f8:   03e00008    jr  ra
  fe14fc:   00000000    nop

00fe1500 <set_static_value>:
  fe1500:   03e00008    jr  ra
  fe1504:   af84fff4    sw  a0,-12(gp)

こういう読み方でもいけるけど、r9を使いまわしているので、なんどもr9に代入する必要ないよねっていう話。

f:id:shigemk2:20150128214326p:plain

f:id:shigemk2:20150128214441p:plain

というか、0x00fe0000を何度も使いまわすのがわかっているなら、グローバルなレジスタに値を代入するだけでいいんじゃないのっていうのがグローバルポインタの考え方

MIPSはグローバルポインタが標準で利用される。というわけで、予めグローバルポインタをmainの前で定義しておく必要がある。

P743

f:id:shigemk2:20150128214743p:plain

(PowerPCも特殊オプションでグローバルポインタを使えちゃう)

遅延スロットはレ点。(登山)あと、nopは新しい規格(mips32)を利用することで消える。

load命令はメモリからとってくる。メモリからとってくる作業は時間がかかるので、遅延スロットはうまくいかないことがあるかもしれない。

疑問

-12って結局なんだよ

グローバルポインタはグローバル変数のど真ん中を指しており、そこを基準に+とか-とか判断している。

0x10000 - 0x00001 = 0xffff

数の循環、符号計算。4桁で計算すると

0x0000 - 0x0001 = 0xffff

みたいな循環になる。

-0x8000〜0x7fff マイナスの部分も有効活用しないとマイナス部分が無駄になってしまう。