pfi seminar 2010/02/18

105
いまどきじゃない アセンブラプログラミング x86アセンブリ言語の基礎からSSEまで 1

Upload: preferred-infrastructure-preferred-networks

Post on 17-Dec-2014

3.404 views

Category:

Documents


1 download

DESCRIPTION

 

TRANSCRIPT

Page 1: PFI Seminar 2010/02/18

いまどきじゃないアセンブラプログラミングx86アセンブリ言語の基礎からSSEまで

1

Page 2: PFI Seminar 2010/02/18

自己紹介

久保田展行

◦ @nobu_k, id:nobu-q

検索エンジンSedueを作ってます

2

Page 3: PFI Seminar 2010/02/18

本日の内容

x86アセンブリ言語の基礎

インラインアセンブラで遊ぶ

SSEを使ってみる

3

Page 4: PFI Seminar 2010/02/18

アセンブリ言語とは

機械語に近いプログラミング言語

◦ 機械語と一対一で対応

4

if (x < 0) {x = -x;

}

mov eax, [ebp + 8]cmp eax, 0jge L1neg eax

L1:

8B 45 08 3D 00 00 00 007D 02 F7 D8

C言語アセンブリ言語

機械語読むのは厳しい

まだマシ

Page 5: PFI Seminar 2010/02/18

アセンブリ言語の特徴

移植性が低い

◦ CPUや処理系によってすべてが変わる

読み書きが大変

◦ 可読性がものすごく低い

高級言語ではできないこともできる

◦ コンパイラが使えない命令も扱える

5

Page 6: PFI Seminar 2010/02/18

今日扱うアセンブリ言語

x86のアセンブリ言語(Intel形式)

なんでx86?

◦ 資料が豊富

◦ ツールが充実

◦ 比較的どこにでもある

◦ SSEを使いたい

6

lヽ ノ l l l l ヽ ヽ)'ーーノ( | | | 、 / l| l ハヽ |ー‐''"l

/ S | | |/| ハ / / ,/ /|ノ /l / l l l| l S ヽl ・ i´ | ヽ、| |r|| | //--‐'" `'メ、_lノ| / ・ /| S l トー-トヽ| |ノ ''"´` rー-/// | S || ・ |/ | l ||、 ''""" j ""''/ | |ヽl ・ || E | | l | ヽ, ― / | | l E || !! | / | | | ` ー-‐ ' ´|| ,ノ| | | !! |

ノー‐---、,| / │l、l |レ' ,ノノ ノハ、_ノヽ

Page 7: PFI Seminar 2010/02/18

SSEとは

x86 CPUの拡張命令

◦ SIMD(Single Instruction Multiple Data)

まだ人間がコンパイラに勝てる分野

◦ 時間の問題かもしれないけれど

SSEはC言語からも使えるが・・・

◦ 選択肢の一つとしてアセンブリ言語を

7

Page 8: PFI Seminar 2010/02/18

前提知識

C言語の知識

◦ ポインタとメモリアドレスの関係

◦ 文法の知識はそんなにいらない

8

Page 9: PFI Seminar 2010/02/18

本日の目標

SSEを独習できるようになる

9

Page 10: PFI Seminar 2010/02/18

ことば

アセンブリ言語

◦ プログラミング言語の一種

アセンブル

◦ アセンブリ言語を機械語に翻訳する作業

アセンブラ

◦ アセンブルするプログラム

「アセンブリ言語」という意味でアセンブラと言うこともよくある

10

Page 11: PFI Seminar 2010/02/18

ことば

____

/ \ /\ キリッ. / (ー) (ー)\

/ ⌒(__人__)⌒ \ < アセンブラとは| |r┬-| | アセンブリ言語を\ `ー'´ / アセンブルする

ノ \ プログラムである/´ ヽ

| l \ヽ -一''''''"~~``'ー--、 -一'''''''ー-、.

ヽ ____(⌒)(⌒)⌒) ) (⌒_(⌒)⌒)⌒))11

Page 12: PFI Seminar 2010/02/18

X86 アセンブリ言語(32BIT)

12

Page 13: PFI Seminar 2010/02/18

プログラミングに必要な要素

変数

◦ レジスタ

◦ メモリ

Cの演算子っぽいもの

◦ 命令

制御構造(ifとかループとか

◦ あとで

13

Page 14: PFI Seminar 2010/02/18

レジスタ

CPUの中にある小さく速いメモリ

◦ スレッドごとに割り当てられる

◦ 個数が限られている

CPU メモリ ディスク

大容量・低速

高速・小容量

L1キャッシュ

L2キャッシュレジスタ

14

Page 15: PFI Seminar 2010/02/18

x86 の汎用レジスタ

8個の32bitレジスタ

それぞれ役割はあるが、絶対ではない

◦ ただし esp は除く(ebpも、かもしれない)

レジスタ 名前 役割

eax Accumulator Register 演算

ebx Base Register 32bit環境では自由

ecx Counter Register カウンタ

edx Data Register eaxの補助

esi Source Index データの読み込み元

edi Destination Index データの書き込み先

ebp Base Pointer フレームポインタ的ななにか

esp Stack Pointer スタックのトップを指す15

Page 16: PFI Seminar 2010/02/18

32bit,16bit,8bitレジスタ

eax

ax

alah

32bit

16bit

8biteax, ebx, ecx, edx

esi,edi,ebp,esp

si,di,bp,sp

32bit

16bit

上位8bit 下位8bit

16

sil,dil,... 8bit

AMD64にはあるっぽい

Page 17: PFI Seminar 2010/02/18

メモリ

レジスタが足りないときはメモリを

[base + index * scale + disp]

base: 汎用レジスタindex: esp以外の汎用レジスタscale: 1, 2, 4, 8disp: 定数

例: [ebp + ecx * 4 + 8]

17

Page 18: PFI Seminar 2010/02/18

メモリの使い方

int a[];a[n];

aのアドレス: eaxn: ecxa[n]: [eax + ecx * 4]

struct {char x;int y;

} s;

sのアドレス: eaxx: [eax]y: [eax + 4]

型情報がないのでバイト数を明示的に指定する必要がある

int *p;char *q;

p: eax*p: dword ptr [eax]

q: esi*q: byte ptr [esi]

配列aのn番目にアクセスしたい

*p, *qとしたい

18パディングに注意

Page 19: PFI Seminar 2010/02/18

命令

シンプルな命令セットで構成される

◦ かなりの数の命令がある(100以上)

一つ一つの命令ができることは少ない

命令 dst, src dst op= src

a = b + c - d;a = b;a += c;a -= d;

たとえば多くの算術命令では

オペランド

ニーモニックmnemonic

2項演算

細かく分解

19複雑な命令は・・・

Page 20: PFI Seminar 2010/02/18

オペランドには何が使える?

レジスタ

メモリ

即値

◦ 生の値(10, 255 etc

◦ srcのみ

命令 dst, src

オペランド

20

Page 21: PFI Seminar 2010/02/18

オペランドの制限

dst,srcに指定可能な組み合わせ

◦ 命令によって異なる

dst src

レジスタ レジスタ

レジスタ 即値

レジスタ メモリ

メモリ レジスタ

メモリ 即値

メモリ-メモリは無い

mov mem2, mem1mov reg, mem1mov mem2, reg

21

Page 22: PFI Seminar 2010/02/18

オペランドの制限2

dst,srcは同じサイズでないとダメ

mov eax, bx

32bit 16bit

mov eax, ebx

22

mov [メモリ], ebx

もう片方のオペランドからサイズを推定してくれる

Page 23: PFI Seminar 2010/02/18

代入・算術命令

2項演算

mov x, yadd x, ysub x, yand x, yor x, yxor x, y

x = y;x += y;x -= y;x &= y;x |= y;x ^= y;

かけ算、わり算、シフトは後ほど!

a = b;a += c;a -= d;

mov a, badd a, csub a, d

a = b + c - d;

23

Page 24: PFI Seminar 2010/02/18

コード例

mov eax, 10mov ebx, 20add eax, ebxmov dword ptr [esi + ecx * 4], eax

24

Page 25: PFI Seminar 2010/02/18

その他の算術命令

単項演算

inc xdec xneg xnot x

x = x + 1;x = x - 1;x = -x;x = ~x;

25

Page 26: PFI Seminar 2010/02/18

/y

掛け算・割り算

div y

mul x edx eaxeax * x

上位32bit

下位32bit

64bit

edx eax

=

eax

edx

商が32bitに収まらないと例外が発生

余り

26

Page 27: PFI Seminar 2010/02/18

符号

2の補数表現

◦ 符号を意識しなくても演算できる

◦ 意識しないとダメなケースもある

255 + 254 = 256 + 253

-1 + -2 = -3

11111111 + 11111110= 1 11111101

8bitの計算

内部的にはどちらも同じことをしている

2進数で表すと

27

Page 28: PFI Seminar 2010/02/18

符号付き命令

idiv, imul

◦ 割り算は符号のありなしで結果が変わる -1/2を符号無しで計算すると0xffffffff/2になる

◦ imulはmulのエイリアス

シフト命令◦ 論理シフト(符号無し)

shr(Javaでいう>>>), shl(<<)

◦ 算術シフト(符号付き) sar(>>), sal(<<)

◦ ローテートもある

28

Page 29: PFI Seminar 2010/02/18

その他の命令

算術命令以外の要素

◦ 比較、論理演算、条件分岐

◦ スタック操作

◦ 関数呼び出し

あとで解説します!

29

Page 30: PFI Seminar 2010/02/18

命令仕様の確認・調査方法

IA-32 アーキテクチャ・ソフトウェア・デベロッパーズ・マニュアル中巻(上下)

アセンブリリストを出力◦ cl /Fa

◦ gcc -S

逆アセンブル

30

Page 31: PFI Seminar 2010/02/18

制御構造は?

if, for, whileどこいってもうたんや

そんな軟弱なものはない!!

◦ あるのは(条件付き)gotoのみ

31

Page 32: PFI Seminar 2010/02/18

ここまででわかったもの

レジスタ

◦ 8個の32bit汎用レジスタ

メモリの使い方(アドレッシング

基本的な算術命令

◦ 指定できるオペランドの制限

32

Page 33: PFI Seminar 2010/02/18

これからわかるもの

ちゃんとしたアセンブリ言語の書き方

条件分岐

ループ

関数の呼び出し方

インラインアセンブラを使って覚えていきます。

33

Page 34: PFI Seminar 2010/02/18

C/C++でアセンブリ言語を使う

Visual C++で頑張るアセンブラプログラミング

34

Page 35: PFI Seminar 2010/02/18

インラインアセンブラ

C/C++の中でアセンブラを使える

VC++(32bit)でやってみよう

◦ AMD64モードでは使えない・・・?

int f() {C言語;__asm {

ここだけアセンブリ言語!!}C言語;

}

35

Page 36: PFI Seminar 2010/02/18

まずは足し算から

#include <stdio.h>int main() {

int a = 10, b = 20, c;c = a + b;printf("%d¥n", c);

}

#include <stdio.h>int main() {

int a = 10, b = 20, c;__asm {

ここで足してみる}printf("%d¥n", c);

}

36

Page 37: PFI Seminar 2010/02/18

足し算

#include <stdio.h>int main() {

int a = 10, b = 20, c;c = a + b;printf("%d¥n", c);

}

#include <stdio.h>int main() {

int a = 10, b = 20, c;__asm {

add a, bmov c, a

}printf("%d¥n", c);

}

できた!?

37

Page 38: PFI Seminar 2010/02/18

オペランドの制約

__asm {add a, bmov c, a

}

両方のオペランドにメモリを指定することはできない

a,b,cは関数mainのローカル変数つまりスタック(メモリ)上にある

__asm {mov eax, aadd a, bmov c, a

}

__asm {mov eax, aadd eax, bmov c, eax

}

int main() {int a = 10, b = 20, c;

aをeaxに置き換え一度レジスタへ

38

Page 39: PFI Seminar 2010/02/18

足し算: 完成版

#include <stdio.h>int main() {

int a = 10, b = 20, c;c = a + b;printf("%d¥n", c);

}

#include <stdio.h>int main() {

int a = 10, b = 20, c;__asm {

mov eax, aadd eax, bmov c, eax

}printf("%d¥n", c);

}

39

Page 40: PFI Seminar 2010/02/18

addを関数にしてみる

#include <stdio.h>int add(int a, int b) {

return a + b;}int main() {

printf("%d¥n",add(10, 20));

}

#include <stdio.h>int add(int a, int b) {

__asm {ここに書く

}}int main() {

printf("%d¥n",add(10, 20));

}

呼ばれる側をインラインアセンブラで実装

40

Page 41: PFI Seminar 2010/02/18

素直に実装: add

#include <stdio.h>int add(int a, int b) {

__asm {mov eax, aadd eax, b

}}int main() {

printf("%d¥n",add(10, 20));

}

41

Page 42: PFI Seminar 2010/02/18

返値はどうやって返す?

#include <stdio.h>int add(int a, int b) {

__asm {mov eax, aadd eax, bmov a, eax

}return a;

}int main() {

printf("%d¥n",add(10, 20));

}

#include <stdio.h>int add(int a, int b) {

__asm {mov eax, aadd eax, b

}}int main() {

printf("%d¥n",add(10, 20));

}

このままでOKでした

return もいらない一度aに入れ直してあげればOK?

実は…

42

Page 43: PFI Seminar 2010/02/18

返値の返し方

呼び出し規約

◦ eax で返値を返す決まりになってる

◦ あとでまた詳しく

32bit以上のものはどうやって返す?,.-─ ─-、─-、

, イ)ィ -─ ──- 、ミヽノ /,.-‐'"´ `ヾj ii / Λ

,イ// ^ヽj(二フ'"´ ̄`ヾ、ノイ{ノ/,/ミ三ニヲ´ ゙、ノi!{V /ミ三二,イ , -─ Yソレ'/三二彡イ .:ィこラ ;:こラ j{V;;;::. ;ヲヾ!V ー '′ i ー ' ソVニミ( 入 、 r j ,′ヾミ、`ゝ ` ー--‐'ゞニ<‐-イ

ヽ ヽ -''ニニ‐ /| `、 ⌒ ,/| > ---- r‐'´ヽ_ |

ヽ _ _ 」

ググレカス [ gugurecus ]

(西暦一世紀前半~没年丌明)43

#include <stdio.h>int add(int a, int b) {

__asm {mov eax, aadd eax, b

}}int main() {

printf("%d¥n",add(10, 20));

}

Page 44: PFI Seminar 2010/02/18

分岐: abs

#include <stdio.h>int abs(int x) {

if (x > 0) return x;else return -x;// return x > 0 ? x : -x;

}int main() {

printf("%d¥n", abs(-7));}

#include <stdio.h>int abs(int x) {

__asm {ここに書く

}}int main() {

printf("%d¥n", abs(-7));}

44

Page 45: PFI Seminar 2010/02/18

x86アセンブリ言語での分岐

if (条件 == true) goto end;

条件がfalseのときに実行したい処理

end:

「条件がtrueだったらgoto」という処理しか実行できない

45

Page 46: PFI Seminar 2010/02/18

if の書き方

if (x < y) {hogehoge

}

if (!(x < y))goto end;

hogehogeend:

if (x >= y)goto end;

hogehogeend:

mov eax, xcmp eax, yjge endhogehoge

end:

ifの中を実行したいので条件を反転させる

!を取る

アセンブリ言語化

!?

46

Page 47: PFI Seminar 2010/02/18

cmp & jmp

条件分岐=比較&ジャンプ

cmp

jge

◦ 条件付きジャンプ命令(ブランチ命令)

47

mov eax, xcmp eax, yjge endhogehoge

end:

Page 48: PFI Seminar 2010/02/18

cmpは何をするのか

値の比較を行う

実は引き算をしている◦ dstの値を変更しない引き算

演算結果に関する情報をフラグレジスタにセット

48

x == yx > yx < y

x - y == 0x - y > 0x - y < 0

減算結果を0と比較すると大小関係が分かる

Page 49: PFI Seminar 2010/02/18

フラグレジスタ(eflags)

32bitのレジスタ

各bitが状態を表す IA-32 インテルアーキテクチャソフトウェア・デベロッパーズ・マニュアル上巻より

よく使うのは CF, ZF, SF, OF の4つ

49

Page 50: PFI Seminar 2010/02/18

CF, ZF, SF, OF

フラグ

名前 意味 例(8bit)

CF キャリーフラグ

計算結果がレジスタのサイズに収まらなかった

3-255=2

ZF ゼロフラグ 計算結果が0になった 4-4=0

SF サインフラグ 計算結果が符号付きになった(最上位bitが1になった)

5-7=-2

OF オーバーフローフラグ

符号付き演算の結果がオーバーフローした

127+1=-128

-120-9=127

cmpにより、これらのフラグが変化する

50

Page 51: PFI Seminar 2010/02/18

条件付きジャンプ

フラグレジスタの値に応じてジャンプ

◦ jcc命令

命令 条件

jc CF=1

jnc CF=0

jz ZF=1

jnz ZF=0

js SF=1

jns SF=0

jo OF=1

jno OF=0

51

Page 52: PFI Seminar 2010/02/18

ジャンプ命令のエイリアス

エイリアスがたくさんある

詳しくはマニュアルを:jcc

わしのエイリアスは108式まであるぞ

比較演算子 対応する命令 フラグ条件

x=y je, jz ZF=1

x!=y jne,jnz ZF=0

x<y jl, jnge (SF XOR OF)=0

x<=y jle,jng ((SF XOR OF) OR ZF)=1

x>y jg, jnle ((SF XOR OF) OR ZF)=0

x>=y jge, jnl (SF XOR OF)=1

cmp x, y としたときのジャンプ表(符号付き比較の場合)

52

Page 53: PFI Seminar 2010/02/18

符号無し条件分岐

比較演算子 対応する命令 フラグ条件

x=y je, jz ZF=1

x!=y jne,jnz ZF=0

x<y jb,jnae CF=1

x<=y jbe,jna (CF OR ZF)=1

x>y ja, jnbe (CF OR ZF)=0

x>=y jae, jnb CF=0

cmp x, y としたときのジャンプ表(符号無し比較の場合)

53

Page 54: PFI Seminar 2010/02/18

条件分岐: abs

完成させてみる

if (x > 0) return x;else return -x;

if (x < 0) x = -x;return x;

if (x >= 0) goto L1;x = -x;

L1:return x;

mov eax, xcmp eax, 0jge L1neg eax

L1:

54

elseを消す

gotoに直す

Page 55: PFI Seminar 2010/02/18

条件分岐: abs

#include <stdio.h>int abs(int x) {

if (x > 0) return x;else return -x;// return x > 0 ? x : -x;

}int main() {

printf("%d¥n", abs(-7));}

#include <stdio.h>int abs(int x) {

__asm {mov eax, xcmp eax, 0jge L1neg eax

L1:}

}int main() {

printf("%d¥n", abs(-7));}

55

returnは丌要

Page 56: PFI Seminar 2010/02/18

ループとメモリ参照: strlen

#include <stdio.h>int strlen(const char* s) {

int i = 0;while (s[i]) i++;return i;

}int main() {

printf("%d¥n",strlen("abcdef"));

}

#include <stdio.h>int strlen(const char* s) {

__asm {ここに書く

}}int main() {

printf("%d¥n",strlen("abfdef"));

}

56

Page 57: PFI Seminar 2010/02/18

ループ

基本は if と goto

int i = 0;while (s[i]) i++;

int i = 0;L1:

if (s[i] == 0) goto L2;i++;goto L1;

L2:

int i = 0;L1:

cmp s[i], 0je L2inc ijmp L1

L2:

あとはメモリ参照をどうするか

57

ifとgotoに変換

部分的にアセンブリ言語に

Page 58: PFI Seminar 2010/02/18

メモリの使い方[base + index * n + disp]

n: 1, 2, 4, 8disp: 即値

メモリからはオペランドのサイズがわからないので、念のため明示的に指定する (記法はアセンブラ依存

int i;const char* s;cmp s[i], 0

ecx = 0 ; iの代わりedx = s ; sの代わりcmp edx[ecx], 0

xor ecx, ecxmov edx, scmp [edx + ecx], 0

xor ecx, ecxmov edx, scmp byte ptr [edx + ecx], 0

雰囲気としては・・・

58

Page 59: PFI Seminar 2010/02/18

メモリの使い方

xor ecx, ecxmov edx, s

L1:cmp byte ptr [edx + ecx], 0je L2inc ecxjmp L1

L2:mov eax, ecx

int i = 0;L1:

cmp s[i], 0je L2inc ijmp L1

L2:

59

ecxをeaxにすることで最後のmovをなくすこともできる

今書いたものに置き換え

Page 60: PFI Seminar 2010/02/18

ループとメモリ参照: strlen

60

#include <stdio.h>int strlen(const char* s) {

int i = 0;while (s[i]) i++;return i;

}int main() {

printf("%d¥n",strlen("abcdef"));

}

#include <stdio.h>int strlen(const char* s) {

__asm {xor eax, eaxmov edx, s

L1:cmp byte ptr[edx+eax],0je L2inc eaxjmp L1

L2:}

}int main() {

printf("%d¥n",strlen("abfdef"));

}

Page 61: PFI Seminar 2010/02/18

フラグレジスタの補足

変化する条件は?

◦ なにか演算を行う

add や and などでも変化する

mov ecx, nLOOP:

ループの処理

dec ecxjnz LOOP

N回ループのイディオム

test eax, eaxjnz NONZERO

0だったときの処理

NONZERO:

0チェックのイディオム

cmpの場合 cmp eax, 0 とするが、即値(32bit)分命令長が長くなる。test eax, eax なら2バイトで済む。

dec ecxでZFが立つとループ終了

61

testはcmpの&演算版

Page 62: PFI Seminar 2010/02/18

関数呼び出し自分で作った関数を__asmの中から呼んでみる

#include <stdio.h>int add(int a, int b) {

__asm {mov eax, aadd eax, b

}}int main() {

printf("%d¥n",add(10, 20));

}

#include <stdio.h>int add(int a, int b) {

__asm {mov eax, aadd eax, b

}}int main() {

int r; // 返値用__asm {

ここでaddを呼び出すmov r, eax

}printf("%d¥n", r);

}

62

Page 63: PFI Seminar 2010/02/18

関数呼び出し:必要な処理

引数を渡す

関数を実行する

関数から戻ってくる

後始末

全体で統一する必要がある

◦ 呼び出し規約

63

Page 64: PFI Seminar 2010/02/18

呼び出し規約

呼び出し規約で定義されるもの◦ 引数の渡し方 スタックで渡す?レジスタで渡す?

◦ 返値の扱い方 eaxで返す?他の手段で返す?

◦ レジスタの使い方 レジスタの値は自由に変えちゃってOK?

いろいろ種類がある◦ 今日扱うのは cdecl

64

Page 65: PFI Seminar 2010/02/18

cdecl

x86な環境ではよく使われている

仕様

◦ 引数はスタック経由で渡す

◦ 返値はeaxで返す(float,doubleの場合はst(0))

◦ eax,ecx,edxは自由に使える

それ以外は保存しなければならない

FPUに関しては今日は扱わない

65

Page 66: PFI Seminar 2010/02/18

引数:スタックメモリ

関数用に確保されているメモリ領域

ローカル変数もここに確保される

スタック操作は push/pop 命令で行う

66

Page 67: PFI Seminar 2010/02/18

push/pop

スタック操作用の命令

push eax

・・・

eaxの値

pop eax

・・・

x

成長方向

67

x

Page 68: PFI Seminar 2010/02/18

スタックとメモリアドレス

・・・

0x00000000

メモリアドレスの小さい方向に向かって伸びる

0xffffffff

esp

push eaxsub esp, 4mov [esp], eax

等価

pushed

メモリアドレス

pop eaxmov eax, [esp]add esp, 4

等価

espはスタックのトップを指す

68

Page 69: PFI Seminar 2010/02/18

引数の渡し方再び

引数をpushする順序も決まっている

後ろの引数からスタックに積む

関数呼び出しは call 命令で

int x, y;add(x, y);

push ypush xここでaddを呼び出す

69

Page 70: PFI Seminar 2010/02/18

関数呼び出し: call命令

...call add...

int add(int x, int y) {__asm {

mov eax, xadd eax, y

}}

関数の先頭アドレスまでジャンプ!

callの次の命令のところまでジャンプして戻る

70

どうやって戻ってくる?

call命令の次の命令のアドレスが分かればOK

EIP レジスタから取得する

Page 71: PFI Seminar 2010/02/18

EIP レジスタ

特殊なレジスタ

◦ 次に実行する命令のアドレスを持つ

プログラムカウンタ(pc)

インストラクションポインタ(ip)

op1 hoge, hogecall addop2 hoge, hoge

call を実行する段階では、eipはop2を指している

71

eip

Page 72: PFI Seminar 2010/02/18

call/ret

・・・

引数2

引数1

・・・

EIP

call Function次の命令

Function()......ret

EIPをpopして、ジャンプ

pop return_addrjmp return_addr

push eipjmp Function

72

Page 73: PFI Seminar 2010/02/18

ret 命令

自分で書いて良い?

◦ インラインアセンブラではダメ!

後処理を自分で正しく書けるならOK

73

int add(int x, int y) {__asm {

mov eax, xadd eax, yret

}コンパイラによって生成される後処理用コードret

}

自分でretを呼ぶと後処理が実行されない

Page 74: PFI Seminar 2010/02/18

スタックの掃除

・・・

引数3

引数2

・・・

引数1

esp

retで呼び出し元へ戻ってきたが、スタックにはゴミ(引数)が残っている

push 引数3push 引数2push 引数1call Function; 帰ってきた

pop regpop regpop reg

add esp, 引数のバイトサイズ

cdeclでは呼び出し元(caller)が後始末をすることになっている。

Win32 APIの呼び出し規約、stdcallでは呼び出され側(callee)が後始末をする。74

3個分pop

Page 75: PFI Seminar 2010/02/18

関数呼び出し: add

75

#include <stdio.h>int add(int a, int b) {

__asm {mov eax, aadd eax, b

}}int main() {

int r; // 返値用__asm {

push 20push 10call addadd esp, 8mov r, eax

}printf("%d¥n", r);

}

#include <stdio.h>int add(int a, int b) {

__asm {mov eax, aadd eax, b

}}int main() {

printf("%d¥n",add(10, 20));

}

Page 76: PFI Seminar 2010/02/18

補足:関数呼び出しとスタック

76

function:push ebpmov ebp, esp

関数の処理

pop ebpret

・・・

引数2

引数1

・・・

EIP

古いebp ebp

esp

関数はこのように書く習慣がある

Page 77: PFI Seminar 2010/02/18

補足:ローカル変数

77

・・・

引数2

引数1

・・・

EIP

古いebp

ローカル変数

ローカル変数

ローカル変数function:push ebpmov ebp, espsub esp, 12

関数の処理

add esp, 12pop ebpret

ebp

esp

[ebp - 4]

[ebp - 8]

[ebp - 12]

ローカル変数も自給自足

Page 78: PFI Seminar 2010/02/18

補足:引数へのアクセス

78

int add(int a, int b) {__asm {

mov eax, aadd eax, b

}}

int add(int a, int b) {__asm {

mov eax, [ebp + 8]add eax, [ebp + 12]

}}

VC++の場合は、インラインアセンブラが自動で置き換えてくれる

・・・

引数2

引数1

・・・

EIP

古いebp

ローカル変数

ローカル変数

ローカル変数

ebp

esp

[ebp - 4]

[ebp - 8]

[ebp - 12]

[ebp + 8]

[ebp + 12]

Page 79: PFI Seminar 2010/02/18

ここまでのまとめ

x86アセンブリ言語の基礎

インラインアセンブラを使った

◦ 単純な演算

◦ 関数記述

◦ 条件分岐

◦ ループ

◦ メモリアクセス

◦ 関数呼び出し

79

Page 80: PFI Seminar 2010/02/18

SSE

80

Page 81: PFI Seminar 2010/02/18

SSE: Streaming SIMD Extensions

SIMD

◦ Single Instruction Multiple Data

バージョン

◦ SSE, SSE2, SSE3, etc

Pentium4 ならSSE2までOK

81

S3 S2 S1 S0

D3 D2 D1 D0

S3+D3 S2+D2 S1+D1 S0+D0

+

画像処理などで大活躍

Page 82: PFI Seminar 2010/02/18

SSEを使用可能かチェック

CPUID命令

◦ CPUの情報を取得するための命令

◦ 特定のバージョンのSSEが使えるかどうか

◦ 本日は省略

今日はSSE2まで

◦ たぶんみんな使える

たぶん 使えなくても落ちるだけなのでだいじょうぶ

82

Page 83: PFI Seminar 2010/02/18

SSEのレジスタ

mm0~mm7の8個

◦ 64bitレジスタ

◦ MMX

◦ 整数演算

xmm0~xmm7の8個

◦ 128bitレジスタ

◦ 整数演算&浮動小数点数演算

◦ AMD64だとさらに8本追加されている

83

mm0

mm1

xmm0

mm2

mm3

mm4

mm5

mm6

mm7

xmm1

xmm2

xmm3

xmm4

xmm5

xmm6

xmm7

Page 84: PFI Seminar 2010/02/18

mmレジスタ

84

byte byte byte byte byte byte byte byte

word word word word

dword dword

packed byte

packed word

packed double word

64bit

x87の浮動小数点数演算と同時に使用することはできない

SIMD前提のレジスタ

Page 85: PFI Seminar 2010/02/18

xmmレジスタ

float, double(SSE2)を扱える

mmレジスタ2個分の働き(SSE2)

85

float float float float

float

double double

double

128bit

packed

single precision

scalar

single precision

scalar

double precision

packed

double precision

x87の浮動小数点数演算と同時に使用できる(しないけど

Page 86: PFI Seminar 2010/02/18

時間がないのでサンプルで

エセαブレンドを実装する

86

void blend(float *dst, const float *src, float a, int n) {int i;for (i = 0; i < n; i++)

dst[i] = (1 - a) * dst[i] + a * src[i];}

void blend(float *dst, const float *src, float a, int n) {int i;for (i = 0; i < n; i++)

dst[i] = dst[i] + a * (src[i] - dst[i]);}

乗算を減らしておく

その前に・・・

// 4個ずつまとめて計算したい

Page 87: PFI Seminar 2010/02/18

xmmに値をロード

87

// edi=dst, esi=srcmovaps xmm1, [edi]movaps xmm2, [esi]

dst,srcをロード

movss xmm0, a

aをロード

dst[i+3] dst[i+2] dst[i+1] dst[i+0]

a

src[i+3] src[i+2] src[i+1] src[i+0]

xmm0

xmm1

xmm2

Page 88: PFI Seminar 2010/02/18

転送命令:float, double用

88

float *p;mov eax, pmovss xmm0, [eax]

*p xmm0

float p[];mov eax, pmovaps xmm0, [eax]

p[3] p[2] p[1] p[0] xmm0

mov(a|u)??ss: floatsd: doubleps: float * 4pd: double * 2

movap?: 16バイトアラインメントを前提

movup?: アラインメントされてなくても大丈夫

Page 89: PFI Seminar 2010/02/18

計算部分

89

dst[i] = dst[i] + a * (src[i] - dst[i]);

xmm0 = axmm1 = dst[i];xmm2 = src[i];

xmm2 -= xmm1;xmm2 *= xmm0;xmm1 += xmm2;

dst[i] = xmm1;

float s = src[i];s -= dst[i];s *= a;dst[i] += s;

mov edi, dstmov esi, src

movss xmm0, amovaps xmm1, [edi]movaps xmm2, [esi]

subps xmm2, xmm1mulps xmm2, xmm0addps xmm1, xmm2

dst[i] = xmm1;

Page 90: PFI Seminar 2010/02/18

subps

90

D3 D2 D1 D0

S3 S2 S1 S0

D3-S3 D2-S2 D1-S1 D0-S0

ーxmm1

xmm2

subps xmm2, xmm1

xmm2

末尾のpsをpdにするとdouble用の命令になる

Page 91: PFI Seminar 2010/02/18

mulps・・・の前に

91

axmm0

今のαは・・・

a a a axmm0

こうしないと4個同時に乗算できない

movss xmm0, a

Page 92: PFI Seminar 2010/02/18

シャッフル

92

shufps xmm0, xmm0, 0

a a a axmm0

dd cc bb aashufps dst, src, imm8 imm8

レジスタ番号(2bit)

src用 dst用

D3 D2 D1 D0dst

S3 S2 S1 S0src

S2 S2 D1 D3dst

10 10 01 11imm8

7 0 (bit)

Page 93: PFI Seminar 2010/02/18

計算結果をメモリへ転送

93

mov edi, dstmov esi, src

movss xmm0, ashufps xmm0, xmm0, 0movaps xmm1, [edi]movaps xmm2, [esi]

subps xmm2, xmm1mulps xmm2, xmm0addps xmm1, xmm2

dst[i] = xmm1;

mov edi, dstmov esi, src

movss xmm0, ashufps xmm0, xmm0, 0movaps xmm1, [edi]movaps xmm2, [esi]

subps xmm2, xmm1mulps xmm2, xmm0addps xmm1, xmm2

movntps [edi], xmm1

Page 94: PFI Seminar 2010/02/18

キャッシュを意識した転送

94

S3 S2 S1 S0xmm1

αブレンド後の結果を4個まとめて転送

D3 D2 D1 D0dst

dstへ書き込んだら、同じ場所へはもうアクセスしない

キャッシュする意味がない(もったいない

movnt??命令はキャッシュを有効活用するためのヒントを不える

Page 95: PFI Seminar 2010/02/18

ループをつけて完成

95

void blend(float *dst, const float *src, float a, int n) {__asm {

movss xmm0, amov edi, dstmov esi, srcmov eax, nshufps xmm0, xmm0, 0 // [a, a, a, a]

L1:movaps xmm1, [edi]movaps xmm2, [esi]subps xmm2, xmm1 // src - dstmulps xmm2, xmm0 // * aaddps xmm1, xmm2 // + dstmovntps [edi], xmm1 // dstへ結果をコピーadd esi, 16 // float 4個分ポインタを進めるadd edi, 16sub eax, 4 // n -= 4jnz L1 // if (n == 0) break;

}

プログラムを簡単にするために、nが4の倍数であることを仮定してます

16-byte alignedであることを仮定しています

Page 96: PFI Seminar 2010/02/18

気になるパフォーマンスは

環境◦ OS: Ubuntu9.10

◦ CPU Intel Core2 Quad 3.0GHz(AMD64)

◦ メモリ 8GB

コンパイラとコンパイルオプション◦ g++ 4.4.1, オプション -O3 -msse2

2^28要素のfloat配列を使ってαブレンド 結果

◦ Cで書いたもの: 0.58sec

◦ SSEバージョン: 0.50sec

◦ 約1.15倍速 ちょっと残念な結果に・・・

96

Page 97: PFI Seminar 2010/02/18

その他のSSE命令

多すぎて全部紹介できません

整数の飽和演算

パックド論理演算(4個同時にandとか)

平方根や絶対値の同時計算

マスク生成用比較命令◦ 条件を満たした要素が0xff..ffになる

使いどころが分からない命令も・・・◦ movmskpsってなんに使うんですか?

97

Page 98: PFI Seminar 2010/02/18

SSEまとめ

使えるレジスタ・命令が増えただけ

◦ プログラミングの基本は一緒

基本さえ押さえてしまえば調べながら自力でプログラムを書ける

98

Page 99: PFI Seminar 2010/02/18

まとめ

99

Page 100: PFI Seminar 2010/02/18

今日やったこと

x86アセンブリ言語の基礎

x86アセンブリ言語の書き方

◦ 演算、条件分岐、関数呼び出し

SSEの概要とちょっとしたサンプル

100

Page 101: PFI Seminar 2010/02/18

おまけ

101

Page 102: PFI Seminar 2010/02/18

Xbyak(カイビャック)

x86, x64用JITアセンブラ for C++

◦ Windows, Mac, Linuxで使えます

特徴

◦ 関数単位で記述

◦ 実行時に定数を埋め込むこんだりできる

◦ 動的コード生成

条件に合わせて最適化可能

インラインアセンブラと比較して

◦ どの環境でも同じ記法が使えるので便利

102

Page 103: PFI Seminar 2010/02/18

Xbyak:続きはウェブで!

103

http://homepage1.nifty.com/herumi/soft/xbyak.html

Page 104: PFI Seminar 2010/02/18

MASM32

Windows Driver Kit(旧DDK)に入っているmasmに皮をかぶせたもの

◦ masm32.com

フルアセンブリで記述可

サンプルコードもいっぱい

Win32APIも簡単に呼べる

マクロで楽々プログラミング

ぐぐってみてね

104

Page 105: PFI Seminar 2010/02/18

ご静聴ありがとうございました

急ぎ足になってしまってすみません

少しでもアセンブリ言語を学ぶハードルが低くなればうれしいです!!

105