Cソースからアセンブリがどのように生成されたかを示すために、分解を分解しました。edx
にeax
に
pushl %ebp
movl %esp,%ebp
移動
x
、
y
:=
x
8(%ebp)
、= y
12(%ebp)
、16(%ebp)
= z
arith:
スタックフレームを作成
movl 8(%ebp),%eax
movl 12(%ebp),%edx
t1 = x + y
。
leal
(負荷実効アドレス)
edx
と
eax
を追加し、そして
t1
は
ecx
であろう。下の2つのステップで
leal (%edx,%eax),%ecx
int t4 = y * 48;
、3を掛け、次いで16
t4
によって最終的
edx
であろう:
edx
に2を掛け、edx
を結果に加えます。 edx = edx * 3
:
leal (%edx,%edx,2),%edx
左に4ビットシフトします。 16を掛け:
sall $4,%edx
int t2 = z+t1;
。
ecx
最初が
z
命令
ecx
の終わりに、
16(%ebp)
であり、
t1
を保持
t2
を保持する。
addl 16(%ebp),%ecx
int t5 = t3 + t4;
。
t3
は、単に
x + 4
であり、
t3
を計算して保存するのではなく、
t3
の式がインラインに配置されています。この指示には、
(x+4) + t4
があり、これは
t3
+
t4
と同じです。これは、
edx
(
t4
)と
eax
(
x
)を追加し、その結果を得るために
オフセットとして4を加えます。
leal 4(%edx,%eax),%eax
int rval = t2 * t5;
かなりこれをストレートフォワード。 ecx
はt2
を表し、eax
はt5
を表す。戻り値は、eax
を通じて呼び出し元に戻されます。
imull %ecx,%eax
スタックフレームを破棄し、
esp
と
ebp
を復元:ルーチンから
movl %ebp,%esp
popl %ebp
戻る:
ret
この例から、あなたがいることがわかりますレスルtは同じですが、構造は少し異なります。ほとんどの場合、このコードはある種の最適化でコンパイルされているか、誰かがそれを書いてポイントを立証しています。
他の人からも言われているように、逆アセンブリからソースに正確に戻ることはできません。アセンブリを読んでいる人が同等のCコードを思いつくのは、その解釈に依存します。あなたがLinux上で次の操作を行うことができ、学習アセンブリとあなたのCプログラムの分解を理解することを助けるために
:
ソースを埋め込みますデバッグ情報(-g
)でコンパイルします。
gcc -c -g arith.c
を
64ビットマシンを使用している場合は、-m32
フラグを使用して32ビットバイナリを作成するようにコンパイラに指示することができます(以下の例ではそうしました)。
objdump -d -S arith.o
-d
=解体、-S
=ディスプレイソース:それは元のインターリーブだと
使用して、objdumpのは、オブジェクトファイルをダンプします。使用している例のAT & T構文よりも好きな場合は、-M intel-mnemonic
を追加してIntel ASM構文を使用できます。
出力:
arith.o: file format elf32-i386
Disassembly of section .text:
00000000 <arith>:
int arith(int x, int y, int z)
{
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 83 ec 20 sub $0x20,%esp
int t1 = x+y;
6: 8b 45 0c mov 0xc(%ebp),%eax
9: 8b 55 08 mov 0x8(%ebp),%edx
c: 01 d0 add %edx,%eax
e: 89 45 fc mov %eax,-0x4(%ebp)
int t2 = z+t1;
11: 8b 45 fc mov -0x4(%ebp),%eax
14: 8b 55 10 mov 0x10(%ebp),%edx
17: 01 d0 add %edx,%eax
19: 89 45 f8 mov %eax,-0x8(%ebp)
int t3 = x+4;
1c: 8b 45 08 mov 0x8(%ebp),%eax
1f: 83 c0 04 add $0x4,%eax
22: 89 45 f4 mov %eax,-0xc(%ebp)
int t4 = y * 48;
25: 8b 55 0c mov 0xc(%ebp),%edx
28: 89 d0 mov %edx,%eax
2a: 01 c0 add %eax,%eax
2c: 01 d0 add %edx,%eax
2e: c1 e0 04 shl $0x4,%eax
31: 89 45 f0 mov %eax,-0x10(%ebp)
int t5 = t3 + t4;
34: 8b 45 f0 mov -0x10(%ebp),%eax
37: 8b 55 f4 mov -0xc(%ebp),%edx
3a: 01 d0 add %edx,%eax
3c: 89 45 ec mov %eax,-0x14(%ebp)
int rval = t2 * t5;
3f: 8b 45 f8 mov -0x8(%ebp),%eax
42: 0f af 45 ec imul -0x14(%ebp),%eax
46: 89 45 e8 mov %eax,-0x18(%ebp)
return rval;
49: 8b 45 e8 mov -0x18(%ebp),%eax
}
4c: c9 leave
4d: c3 ret
あなたは最適化せずに、コンパイラはあなたが持っている例よりも大きなバイナリを生成し、見ることができるように。コンパイル時にコンパイル最適化フラグを追加することができます(つまり、-O1
、-O2
、-O3
)。最適化レベルが高いほど、分解がより抽象的に見えるようになります。例えば、単にレベル1最適化(gcc -c -g -O1 -m32 arith.c1
)と、生成されたアセンブリコードは多くの短い
:
00000000 <arith>:
int arith(int x, int y, int z)
{
0: 8b 4c 24 04 mov 0x4(%esp),%ecx
4: 8b 54 24 08 mov 0x8(%esp),%edx
int t1 = x+y;
8: 8d 04 11 lea (%ecx,%edx,1),%eax
int t2 = z+t1;
b: 03 44 24 0c add 0xc(%esp),%eax
int t3 = x+4;
int t4 = y * 48;
f: 8d 14 52 lea (%edx,%edx,2),%edx
12: c1 e2 04 shl $0x4,%edx
int t5 = t3 + t4;
15: 8d 54 11 04 lea 0x4(%ecx,%edx,1),%edx
int rval = t2 * t5;
19: 0f af c2 imul %edx,%eax
return rval;
}
1c: c3 ret
あなたがコンパイルされたとき、あなたは何の最適化レベルを使用していますか?行ごとの変換を行う場合は-O0を使用し、そうでない場合は最適化を考慮する必要があります。 – Maz
...最適化されたコードを元のCステートメントの順序に戻すことは完全に可能ではありません。 –
これは簡単なコンパイルですか?幾分最適化されているようです。関数が計算する代数式を書き留めて、それを発見できるかどうかを確認します。 –