32ビットと64ビットの逆アセンブル

お遊びです。

またまた Windows 8 は 32bit か64bit か

という無責任記事がまだ人気があるらしく、ずいぶんご覧頂いています。ありがとうございます。

じゃぁ、本当に 32 ビットと 64 ビットとのコードの大きさとか性能差ってあるのか。
実際に試してみました。と言っても、Windows には標準でコンパイラが付いていないので、 SUSE Linux の x64 版でのプログラムコードの違いを見てみました。

-テストコード-

/* test.c*/
int x=5;
int y=9;
int z=0;
int main()
{
z=x*y;
}


これみて、「なんで変数がグローバルなのよ、馬鹿じゃないの」と思われた方。鋭いですね。しかし内部変数だと、スタックポインタからの相対値なので、32ビットコードも64ビットコードも命令には変わりなくて、面白みがありませんでした。実際にオペランドアドレスがどうなるのか、と考えてみたら、グローバル変数を使ってみると、ずばりでした。20年ぶりのC言語なので大目に見てください。ご批判もあるでしょうね。

このコードを64ビットネイティブでコンパイルした結果と、32ビットオプションでコンパイルしてできたバイナリのサイズは次の通りでした。

ls -l test64 test32
-rwxr-xr-x 1 me users 9773 Aug 28 18:54 test32
-rwxr-xr-x 1 me users 11852 Aug 28 18:54 test64

こんな簡単なソースコードでも、実際コンパイルしてみると1~2割程度、64ビットコードの方が大きくなったようです。main() の処理より前後に付いているOS用のコードの大きさが実バイナリのサイズの差にあるようです。ただしその周囲のバイナリを見ても64ビットのオペランド処理はほとんどやっていないようです。ただし、x86 なら1バイトの命令でも rXX の64ビットレジスタ同士の演算はプリフィックスが付いた3バイト命令がありました。全体的にはコード自体は32ビットコードの方が複雑でも、1命令自体が1バイトだったりしています。


-32ビットコンパイルと逆アセンブル-
cc -m32 -o test32 test.c
objdump -d test32 > test32.asm

-m32 オプションを付けると32ビットコードを出してくれます。これを objdump -d オプションでダンプすると、逆アセンブルしてくれます。

※ SUSEでは yast > Software Management > から gcc の32ビット環境をインストールします。

main 関数の部分だけ抜き出して見ました。


080483e4 (main):
80483e4: 8d 4c 24 04 lea 0x4(%esp),%ecx
80483e8: 83 e4 f0 and $0xfffffff0,%esp
80483eb: ff 71 fc pushl -0x4(%ecx)
80483ee: 55 push %ebp
80483ef: 89 e5 mov %esp,%ebp
80483f1: 51 push %ecx
80483f2: 83 ec 04 sub $0x4,%esp
80483f5: a1 1c a0 04 08 mov 0x804a01c,%eax
80483fa: 8b 15 10 a0 04 08 mov 0x804a010,%edx
8048400: 0f af c2 imul %edx,%eax
8048403: a3 20 a0 04 08 mov %eax,0x804a020
8048408: 83 c4 04 add $0x4,%esp
804840b: 59 pop %ecx
804840c: 5d pop %ebp
804840d: 8d 61 fc lea -0x4(%ecx),%esp
8048410: c3 ret


20年ぶりに見るi386 アセンブラのコードです。そうだ、C3って ret 命令だったなぁと、割り込みと分岐命令「命」で調べていた、デバッカ開発のプログラマ時代を今更思い出します。この後は 90 (nop) 命令で埋まっています。(おいおい、これじゃ初期のインベーダーゲームと同じだぞ)、nop 命令もしっかりキャッシュされるわけですね。本当は掛け算の命令って何だっけと思って調べてみたのですが、 imul って便利な命令があるんですね。掛け算って cx で loop させるんじゃないんだ。(それじゃ80年代の情報処理試験とおんなじだ)そういえば DEV って便利な命令も 8086 時代に見たことあるしなぁ。8x で始まるのは確かに MOV 直値の命令だったし、 eAX は32ビットのアキュムレータレジスタです。グローバル変数の絶対アドレスを eAX にロードして imul 掛け算しています。

記憶と違って、逆アセンブルしてみると当時全盛だった MASM と違ってニモニックのオペランドの右辺と左辺が違うので、最初戸惑いました。

-64ビットコンパイルと逆アセンブル-

普通に「初めてのC」でやったようなコンパイルです。cc コマンドはオプションなしだと、今動いているプラットフォーム用(64ビット)のコードを吐き出します。


cc -o test64 test.c
objdump -d test64 > test64.asm



00000000004004ec (main):
4004ec: 55 push %rbp
4004ed: 48 89 e5 mov %rsp,%rbp
4004f0: 8b 05 3a 0b 20 00 mov 0x200b3a(%rip),%eax # 601030 (x)
4004f6: 8b 15 1c 0b 20 00 mov 0x200b1c(%rip),%edx # 601018 (y)
4004fc: 0f af c2 imul %edx,%eax
4004ff: 89 05 2f 0b 20 00 mov %eax,0x200b2f(%rip) # 601034 (z)
400505: c9 leaveq
400506: c3 retq



随分すっきりしています。大昔の x86 系のアセンブラより便利な命令が増えたということですね。

しかし、グローバル変数をアキュムレータにロードする場合は eAX を使っています。64ビット命令なら rAX レジスタの表記になります。

何のことはありません。 int x=n ; と書いた時点で、この変数は32ビット変数なのですね。 dword とかの宣言をするとまた変わるのでしょう。rIP 64ビットレジスタからの相対的な32ビット値のオペランドからデータをロードしています。この辺のコードの大きさはデータが32ビットなのであまり変わらないようです。いきなり関数の先頭で rBP のベ64ビットペースレジスタの値を push しています。

ただし便利な命令が増えたせいか、バイナリコードはやけにすっきりしています。

ただ、i486 止まりだった記憶と違ってチラ見した最新の Intel のマニュアルによると、i386 とは違い、x64 では通常の AX,BXや SP,IP ... 以外に r00 - r15 のRISCや 68000 系を思い出す汎用の64 ビットレジスタが16本あるようで(正確には8本でした)、ハードウェアからの割り込みによるスタックの退避などでは、通常の制御レジスタ以外に当然これらの16本の汎用レジスタの Push 操作も行われるのでしょう。プログラマはコードしか見ませんが、ハードウェア的にはシンプルな1命令であっても莫大なメモリアクセスを行います。タスクチェンジなどを行うとページ管理テーブルやディスクリプタ管理テーブル(セグメントテーブル)の膨大な書き換えもあります。おそらく c3 (return) 命令だけでもセグメント切り替えだとか、その前の c9 (leaveq)なんかは全部のレジスタのスタックからの復帰を行うので、大量のメモリアクセスが発生します。デバックに明け暮れた頃、ステップ実行で pop BP して c6 ret するとほっとしたものですが、この命令一発でスタックに積んだキャッシュがモノをいう時ですね。

※ C6 命令はショートジャンプなので、セグメントの切り替えは起こりませんね。間違えです。 jmpf 命令(確か EAh だったはず)

まぁ推測ですけど、32ビットのコードは効率は悪そうですけど、使うレジスタの本数が少ないので激しい割り込みが多いマルチタスク環境でもそこそこ性能は出そうだし、逆に64ビットのコードは、バイナリはそれほど使わなくても実際の動作には随分負担が重そうな感じがします。そもそも32ビットコードでは汎用のr00-r15なんて「何者だ?」の世界だからおそらく無視するでしょう。

コードは1クロック1命令ではありません。 90 nop は 1 clock で実行したような気がしますが、その他の「便利で複雑な命令」は数クロックから数十クロック実行時間がかかります。その辺りは Intel さんのマニュアルにもあるでしょう。

汎用レジスタが多いので、おそらく「素数計算」だとか、浮動小数点演算なんかやらせると、コンパイラの出来次第もありますが絶対64ビットのコードの方が早そうです。

夏休みの終わりに宿題を大急ぎで終えた気分です。Linux のソースより、バイナリから逆アセンブルした結果を調べるという実にひどい悪趣味に最後までお付き合いいただきありがとうございます。

インテルのマニュアルセット

x64のレジスタ拡張 ITmedia
by islandcenter | 2013-08-07 11:00 | 雑文 | Comments(0)