
【プログラミング】C言語で使ってアセンブリ言語の勉強する方法
アセンブリ言語を学ぶ方法は沢山ありますが、少なくとも1つのアセンブリ方言を学ぶ事をお勧めします。
1つの方言を習得すると、他のアセンブリ言語も学び易くなり、コンピュータがどの様に動作するかを深く理解出来ます。
アセンブリを読めないと、コンピュータの仕組みを本当に理解するのは難しいです。
通常は、MIPSやRISC-Vアセンブリから始める事をお勧めします。
これらは非常にシンプルだからです。
然し、今回は貴方がIntel又はAMDプロセッサを使用していると仮定して、x64アセンブリを見ていきます。
C言語からアセンブリを学ぶワークフロー
cc -o program program.c
でコンパイルする。objdump -d -Mintel program | less
を使ってバイナリを逆アセンブルする。main
関数を探し、アセンブリ命令を分析する。最適化なし(デフォルトで -O0
)で cc main.c -o ftoc
とコンパイルします。-O2
の様な最適化フラグを使用すると、異なるアセンブリが生成される可能性がある為、学習には -O0
を使用して下さい。
Cからアセンブリへの変換
アセンブリを学ぶ最も簡単な方法は、先ず簡単なCプログラムを作成し、それをコンパイルし、objdump
を使ってバイナリを逆アセンブルする事です。
この記事ではGhostBSDを使用しますが、Linux、Illumos、FreeBSD、OpenBSD、NetBSD、又はその他のUnix系OSを使用していても問題ありません。
この記事で使用する全てのツールは、既に貴方のコンピュータにインストールされています。
アセンブリにはIntel構文を使用します(objdump
で -Mintel
を指定)。
これにより、命令はAT&Tの movl %ebx, %eax
ではなく、mov eax, ebx
の様に記述されます。
以下は、B.W.カーニハンとD.M.リッチーによる『プログラミング言語C第2版』から直接引用したCコードです。
これは全てのCプログラマーにとって金字塔ともいえる書籍です。
#include <stdio.h>
int main() {
int fahr, celsius;
int lower, upper, step;
lower = 0; /* 温度表の下限 */
upper = 300; /* 上限 */
step = 20; /* きざみ */
fahr = lower;
while (fahr <= upper) {
celsius = 5 * (fahr-32) / 9;
printf("%d\t%d\n", fahr, celsius);
fahr = fahr + step;
}
}
このCプログラムは、華氏(Fahrenheit)から摂氏(Celsius)への変換を行い、0°Fから300°Fまで20°F刻みで表を出力します。
使用されている計算式は celsius = 5 * (fahr - 32) / 9
です。
次に、cc main.c -o ftoc
でコンパイルします。
そして、 objdump -d -Mintel ./ftoc | less
コマンドを実行します。/
キーを押して main
と入力します。
見える内容は、使用しているCPUアーキテクチャやOSによって若干異なります。
あたしの場合、以下の様な結果が表示されます:
00000000002016a0 <main>:
2016a0: 55 push rbp
2016a1: 48 89 e5 mov rbp, rsp
2016a4: 48 83 ec 20 sub rsp, 0x20
2016a8: c7 45 fc 00 00 00 00 mov dword ptr [rbp - 0x4], 0x0
2016af: c7 45 f0 00 00 00 00 mov dword ptr [rbp - 0x10], 0x0
2016b6: c7 45 ec 2c 01 00 00 mov dword ptr [rbp - 0x14], 0x12c
2016bd: c7 45 e8 14 00 00 00 mov dword ptr [rbp - 0x18], 0x14
2016c4: 8b 45 f0 mov eax, dword ptr [rbp - 0x10]
2016c7: 89 45 f8 mov dword ptr [rbp - 0x8], eax
2016ca: 8b 45 f8 mov eax, dword ptr [rbp - 0x8]
2016cd: 3b 45 ec cmp eax, dword ptr [rbp - 0x14]
2016d0: 7f 36 jg 0x201708 <main+0x68>
2016d2: 8b 45 f8 mov eax, dword ptr [rbp - 0x8]
2016d5: 83 e8 20 sub eax, 0x20
2016d8: 6b c0 05 imul eax, eax, 0x5
2016db: b9 09 00 00 00 mov ecx, 0x9
2016e0: 99 cdq
2016e1: f7 f9 idiv ecx
2016e3: 89 45 f4 mov dword ptr [rbp - 0xc], eax
2016e6: 8b 75 f8 mov esi, dword ptr [rbp - 0x8]
2016e9: 8b 55 f4 mov edx, dword ptr [rbp - 0xc]
2016ec: 48 bf d8 04 20 00 00 00 00 00 movabs rdi, 0x2004d8
2016f6: b0 00 mov al, 0x0
2016f8: e8 c3 00 00 00 call 0x2017c0 <printf@plt>
2016fd: 8b 45 f8 mov eax, dword ptr [rbp - 0x8]
201700: 03 45 e8 add eax, dword ptr [rbp - 0x18]
201703: 89 45 f8 mov dword ptr [rbp - 0x8], eax
201706: eb c2 jmp 0x2016ca <main+0x2a>
201708: 8b 45 fc mov eax, dword ptr [rbp - 0x4]
20170b: 48 83 c4 20 add rsp, 0x20
20170f: 5d pop rbp
201710: c3 ret
201711: cc int3
201712: cc int3
201713: cc int3
201714: cc int3
201715: cc int3
201716: cc int3
201717: cc int3
201718: cc int3
201719: cc int3
20171a: cc int3
20171b: cc int3
20171c: cc int3
20171d: cc int3
20171e: cc int3
20171f: cc int3
末尾の int3
命令(オペコード cc
)は、コンパイラがアライメントやデバッグ用のトラップ、或いは無効な制御フローの為に追加したパディングです。
これらはプログラムのロジックには影響しない為、今回は無視出来ます。
全ての数値が16進数で表現されている事に気付くでしょう。
見逃した人の為に、ビット演算について詳しく説明した記事をこちらに書いています。
アセンブリの基礎
アセンブリ言語は、マシン命令に直接対応する低レベル言語です。
主な概念は以下の通りです:
rax
、rbp
、rsp
)。rsp
)とベースポインタ(rbp
)で管理されます。mov
(データ移動)、cmp
(比較)、jmp
(アドレスへのジャンプ)等のコマンド。簡単な部分
整数の定義
最初に気付くのは以下の部分かもしん:
2016af: c7 45 f0 00 00 00 00 mov dword ptr [rbp - 0x10], 0x0
2016b6: c7 45 ec 2c 01 00 00 mov dword ptr [rbp - 0x14], 0x12c
2016bd: c7 45 e8 14 00 00 00 mov dword ptr [rbp - 0x18], 0x14
2016c4: 8b 45 f0 mov eax, dword ptr [rbp - 0x10]
2016c7: 89 45 f8 mov dword ptr [rbp - 0x8], eax
これは以下のCコードに対応します:
lower = 0;
upper = 300;
step = 20;
fahr = lower;
rbp - 0x10
は lower
を、 rbp - 0x14
は upper
を、 rbp - 0x18
は step
を、 rbp - 0x8
は fahr
を格納しています。
命令 mov eax, dword ptr [rbp - 0x10]
は lower
(0) を eax
(rax
レジスタのバイト0-3)にロードします。rax
は関数の戻り値を格納する為に使用されます。mov dword ptr [rbp - 0x8], eax
は lower
を fahr
に代入します。
rbp
は現在のスタックフレームで、 rsp
は現在のスタックポインタです。
プログラムの冒頭では、以下のようなコードが見られます:
2016a0: 55 push rbp
2016a1: 48 89 e5 mov rbp, rsp
2016a4: 48 83 ec 20 sub rsp, 0x20
これらの命令は、 main
関数のスタックフレームを設定します。
push rbp
、mov rbp, rsp
、及び sub rsp, 0x20
は、x64関数の標準的なプロローグを形成します。push rbp
は呼び出し元のベースポインタを保存し、mov rbp, rsp
は現在のスタックポインタを main
のスタックフレームの新しいベースポインタとして設定し、sub rsp, 0x20
はローカル変数とアライメントの為に32バイトを割り当てます。
この部分は全てのコードで見られ、Cコードには直接対応しません。
これはマシンがバックグラウンドで実行する処理だからです。
又、[rbp - 0x10]
の様な記述も見られます。
これは、rbp
レジスタが現在のスタックフレームのベースポインタであり、16進数のオフセット 0x10
(16) を引く事で、そのアドレスに格納されたローカル変数(この場合は lower
)をロードするからです。
printf関数
次に、簡単に見つけられるのは以下の部分です:
2016e6: 8b 75 f8 mov esi, dword ptr [rbp - 0x8]
2016e9: 8b 55 f4 mov edx, dword ptr [rbp - 0xc]
2016ec: 48 bf d8 04 20 00 00 00 00 00 movabs rdi, 0x2004d8
2016f6: b0 00 mov al, 0x0
2016f8: e8 c3 00 00 00 call 0x2017c0 <printf@plt>
これはループ内の printf()
関数に対応します。mov esi, dword ptr [rbp - 0x8]
は fahr
を esi
(printf
の2番目の引数)にロードします。mov edx, dword ptr [rbp - 0xc]
は celsius
を edx
(printf
の3番目の引数)にロードします。movabs rdi, 0x2004d8
はフォーマット文字列 "%d\t%d\n"
のアドレスを rdi
(printf
の1番目の引数)にロードします。mov al, 0x0
は al
を0に設定し、printf
に浮動小数点引数が渡されていない事を示します。
これはx64プロセッサでの要件です。
次に、call 0x2017c0 <printf@plt>
が見られます。call 0x2017c0 <printf@plt>
は、動的リンクされたバイナリで使用されるプロシージャリンケージテーブル(PLT)を介して printf
を呼び出し、実行時に libc.so
内の printf
のアドレスを解決します。
静的リンクされたバイナリでは、後で説明する様に、完全な printf
実装が含まれます。
では、アドレス 0x2004d8
を確認しましょう。
先ず、q
を押して現在の objdump
インスタンスを終了します。
次に、objdump -d -Mintel -s -j .rodata ./ftoc
を実行します。
あたしの場合、出力は以下の様になります:
./ftoc: file format elf64-x86-64
Contents of section .rodata:
2004d8 25640925 640a0000 %d.%d...
Disassembly of section .rodata:
00000000002004d8 <.rodata>:
2004d8: 25 64 09 25 64 and eax, 0x64250964
2004dd: 0a 00 or al, byte ptr [rax]
2004df: 00 <unknown>
特に 2004d8: 25 64 09 25 64 and eax, 0x64250964
を見てみましょう。%
= 0x25d
= 0x64
x64はリトルエンディアンアーキテクチャなので、バイトシーケンス 25 64
は %d
を表します。\t
= 0x09
その後、25 64
が繰り返され、これは %d
を2回使用している為納得できます。
次の行では、2004dd: 0a 00 or al, byte ptr [rax]
があります。\n
= 0x0a
最後の 0x00 は、コンパイラが挿入するヌルターミネータです。
これが printf
の定義場所です。
詳細には触れませんが、アドレス 2017c0
を検索すると、あたしの場合、バイナリの最後にあります。
以下の様な内容が見えます。
00000000002017c0 <printf@plt>:
2017c0: ff 25 aa 21 00 00 jmp qword ptr [rip + 0x21aa] # 0x203970 <printf+0x203970>
2017c6: 68 02 00 00 00 push 0x2
2017cb: e9 c0 ff ff ff jmp 0x201790 <.plt>
宿題として、これが何を意味するのか理解して下さい。
但し、これは動的リンクされたバイナリの printf
です。
代わりに静的リンクされたバイナリを見ると、出力は以下の様になります:
0000000000227320 <printf>:
227320: 55 push rbp
227321: 48 89 e5 mov rbp, rsp
227324: 48 81 ec d0 00 00 00 sub rsp, 0xd0
22732b: 49 89 fa mov r10, rdi
22732e: 84 c0 test al, al
227330: 74 26 je 0x227358 <printf+0x38>
227332: 0f 29 85 60 ff ff ff movaps xmmword ptr [rbp - 0xa0], xmm0
227339: 0f 29 8d 70 ff ff ff movaps xmmword ptr [rbp - 0x90], xmm1
227340: 0f 29 55 80 movaps xmmword ptr [rbp - 0x80], xmm2
227344: 0f 29 5d 90 movaps xmmword ptr [rbp - 0x70], xmm3
227348: 0f 29 65 a0 movaps xmmword ptr [rbp - 0x60], xmm4
22734c: 0f 29 6d b0 movaps xmmword ptr [rbp - 0x50], xmm5
227350: 0f 29 75 c0 movaps xmmword ptr [rbp - 0x40], xmm6
227354: 0f 29 7d d0 movaps xmmword ptr [rbp - 0x30], xmm7
227358: 48 89 b5 38 ff ff ff mov qword ptr [rbp - 0xc8], rsi
22735f: 48 89 95 40 ff ff ff mov qword ptr [rbp - 0xc0], rdx
227366: 48 89 8d 48 ff ff ff mov qword ptr [rbp - 0xb8], rcx
22736d: 4c 89 85 50 ff ff ff mov qword ptr [rbp - 0xb0], r8
227374: 4c 89 8d 58 ff ff ff mov qword ptr [rbp - 0xa8], r9
22737b: 48 8b 05 ae 3c 07 00 mov rax, qword ptr [rip + 0x73cae] # 0x29b030 <__stack_chk_guard>
227382: 48 89 45 f8 mov qword ptr [rbp - 0x8], rax
227386: 48 8d 85 30 ff ff ff lea rax, [rbp - 0xd0]
22738d: 48 89 45 f0 mov qword ptr [rbp - 0x10], rax
227391: 48 8d 45 10 lea rax, [rbp + 0x10]
227395: 48 89 45 e8 mov qword ptr [rbp - 0x18], rax
227399: 48 b8 08 00 00 00 30 00 00 00 movabs rax, 0x3000000008
2273a3: 48 89 45 e0 mov qword ptr [rbp - 0x20], rax
2273a7: 48 8b 3d 22 ea 06 00 mov rdi, qword ptr [rip + 0x6ea22] # 0x295dd0 <__stdoutp>
2273ae: 48 8d 55 e0 lea rdx, [rbp - 0x20]
2273b2: 4c 89 d6 mov rsi, r10
2273b5: e8 46 40 00 00 call 0x22b400 <vfprintf>
2273ba: 48 8b 0d 6f 3c 07 00 mov rcx, qword ptr [rip + 0x73c6f] # 0x29b030 <__stack_chk_guard>
2273c1: 48 3b 4d f8 cmp rcx, qword ptr [rbp - 0x8]
2273c5: 75 09 jne 0x2273d0 <printf+0xb0>
2273c7: 48 81 c4 d0 00 00 00 add rsp, 0xd0
2273ce: 5d pop rbp
2273cf: c3 ret
2273d0: e8 1b cc 00 00 call 0x233ff0 <__stack_chk_fail_local>
2273d5: 66 66 2e 0f 1f 84 00 00 00 00 00 nop word ptr cs:[rax + rax]
又、宿題として、printf@plt
(動的)と printf
(静的)の関数を調べてみて下さい。
静的リンクでは、printf
関数とそれが必要とする全ての関数が含まれますが、動的リンクでは、システム上の何処かにある libc.so
ファイル内のアドレスを指すだけです。
その場合、objdump -d -Mintel /lib/libc.so.7 | less
(OSによってファイル名やパスが異なる場合があります)で確認出来ます。
ここでは以下の様な内容が見られます:
00000000001cd370 <printf@plt>:
1cd370: ff 25 82 1d 01 00 jmp qword ptr [rip + 0x11d82] # 0x1df0f8
1cd376: 68 93 01 00 00 push 0x193
1cd37b: e9 b0 e6 ff ff jmp 0x1cba30 <.plt>
残念ながら、このオブジェクトダンプ内では完全な定義を見つける事は出来ません。
但し、ライブラリ全体をダンプする事で確認出来ます:objdump -d /lib/libc.so.7 > libc_disasm.txt
次に、less libc_disasm.txt
を実行し、/
を押して <printf>
を検索します。
すると、完全な定義が以下の様に見えます(AT&T構文で):
000000000011b220 <printf>:
11b220: 55 pushq %rbp
11b221: 48 89 e5 movq %rsp, %rbp
11b224: 53 pushq %rbx
11b225: 48 81 ec d8 00 00 00 subq $0xd8, %rsp
11b22c: 49 89 fa movq %rdi, %r10
11b22f: 84 c0 testb %al, %al
11b231: 74 29 je 0x11b25c <printf+0x3c>
11b233: 0f 29 85 50 ff ff ff movaps %xmm0, -0xb0(%rbp)
11b23a: 0f 29 8d 60 ff ff ff movaps %xmm1, -0xa0(%rbp)
11b241: 0f 29 95 70 ff ff ff movaps %xmm2, -0x90(%rbp)
11b248: 0f 29 5d 80 movaps %xmm3, -0x80(%rbp)
11b24c: 0f 29 65 90 movaps %xmm4, -0x70(%rbp)
11b250: 0f 29 6d a0 movaps %xmm5, -0x60(%rbp)
11b254: 0f 29 75 b0 movaps %xmm6, -0x50(%rbp)
11b258: 0f 29 7d c0 movaps %xmm7, -0x40(%rbp)
11b25c: 48 89 b5 28 ff ff ff movq %rsi, -0xd8(%rbp)
11b263: 48 89 95 30 ff ff ff movq %rdx, -0xd0(%rbp)
11b26a: 48 89 8d 38 ff ff ff movq %rcx, -0xc8(%rbp)
11b271: 4c 89 85 40 ff ff ff movq %r8, -0xc0(%rbp)
11b278: 4c 89 8d 48 ff ff ff movq %r9, -0xb8(%rbp)
11b27f: 48 8b 1d 3a dc 0b 00 movq 0xbdc3a(%rip), %rbx # 0x1d8ec0
11b286: 48 8b 03 movq (%rbx), %rax
11b289: 48 89 45 f0 movq %rax, -0x10(%rbp)
11b28d: 48 8d 85 20 ff ff ff leaq -0xe0(%rbp), %rax
11b294: 48 89 45 e0 movq %rax, -0x20(%rbp)
11b298: 48 8d 45 10 leaq 0x10(%rbp), %rax
11b29c: 48 89 45 d8 movq %rax, -0x28(%rbp)
11b2a0: 48 b8 08 00 00 00 30 00 00 00 movabsq $0x3000000008, %rax # imm = 0x3000000008
11b2aa: 48 89 45 d0 movq %rax, -0x30(%rbp)
11b2ae: 48 8b 05 1b dd 0b 00 movq 0xbdd1b(%rip), %rax # 0x1d8fd0
11b2b5: 48 8b 38 movq (%rax), %rdi
11b2b8: 48 8d 55 d0 leaq -0x30(%rbp), %rdx
11b2bc: 4c 89 d6 movq %r10, %rsi
11b2bf: e8 cc 11 0b 00 callq 0x1cc490 <vfprintf@plt>
11b2c4: 48 8b 0b movq (%rbx), %rcx
11b2c7: 48 3b 4d f0 cmpq -0x10(%rbp), %rcx
11b2cb: 75 0a jne 0x11b2d7 <printf+0xb7>
11b2cd: 48 81 c4 d8 00 00 00 addq $0xd8, %rsp
11b2d4: 5b popq %rbx
11b2d5: 5d popq %rbp
11b2d6: c3 retq
11b2d7: e8 a4 07 0b 00 callq 0x1cba80 <__stack_chk_fail@plt>
11b2dc: 0f 1f 40 00 nopl (%rax)
簡単ですよね?
次に、もっと分かり憎い部分に進みます!
分かり憎い部分
whileループの条件は以下の様に構築されます:
2016ca: 8b 45 f8 mov eax, dword ptr [rbp - 0x8]
2016cd: 3b 45 ec cmp eax, dword ptr [rbp - 0x14]
2016d0: 7f 36 jg 0x201708 <main+0x68>
これは、whileループの条件、つまり fahr
が upper
以下かどうかをチェックします。mov eax, dword ptr [rbp - 0x8]
は fahr
を eax
にロードします。cmp eax, dword ptr [rbp - 0x14]
は fahr
を upper
(rbp - 0x14
に格納)と比較します。
この2つで fahr <= upper
を表現します。jg 0x201708
は、fahr
が upper
より大きい場合にアドレス 201708
(ループの終わり)にジャンプします。
これは fahr <= upper
の否定であり、fahr
が upper
以下の場合にループが継続します。
ループ内では以下のようなコードが見られます:
2016d2: 8b 45 f8 mov eax, dword ptr [rbp - 0x8]
2016d5: 83 e8 20 sub eax, 0x20
2016d8: 6b c0 05 imul eax, eax, 0x5
2016db: b9 09 00 00 00 mov ecx, 0x9
2016e0: 99 cdq
2016e1: f7 f9 idiv ecx
2016e3: 89 45 f4 mov dword ptr [rbp - 0xc], eax
これは celsius = 5 * (fahr - 32) / 9
を計算します。mov eax, dword ptr [rbp - 0x8]
は fahr
を eax
にロードします。sub eax, 0x20
は eax
から32(0x20)を引き、Cコードの fahr - 32
に相当します。imul eax, eax, 0x5
は eax
を5倍し、Cコードの 5 * (fahr - 32)
に相当します。mov ecx, 0x9
は9を ecx
(除数)にロードします。cdq
は eax
を符号拡張して edx:eax
にし、idiv
命令に備えます。
これは edx:eax
の64ビット値を符号付き除算する為に使用され、5 * (fahr - 32) / 9
の負の数を正しく処理します。idiv ecx
は edx:eax
を ecx
(9)で割り、商を eax
に格納します。mov dword ptr [rbp - 0xc], eax
は結果を celsius
(rbp - 0xc
)に格納します。
詰り、Cコードの1行に対してアセンブリ命令が7行必要です。
次に、以下の計算が行われます:
2016fd: 8b 45 f8 mov eax, dword ptr [rbp - 0x8]
201700: 03 45 e8 add eax, dword ptr [rbp - 0x18]
201703: 89 45 f8 mov dword ptr [rbp - 0x8], eax
これまでに多くの説明を受けたので、これは fahr = fahr + step;
を意味する事が分かるはずです。
その後、以下が見られます:
201706: eb c2 jmp 0x2016ca <main+0x2a>
これは、whileループの先頭(アドレス 2016ca
)に戻り、fahr
が未だ upper
以下かどうかをチェックする事を意味します。
答えが「false
」の場合、ループは終了し、「true
」の場合は続行します。
最後に、以下があります:
201708: 8b 45 fc mov eax, dword ptr [rbp - 0x4]
20170b: 48 83 c4 20 add rsp, 0x20
20170f: 5d pop rbp
201710: c3 ret
これはスタックフレームをクリーンアップし、main
から戻ります。mov eax, dword ptr [rbp - 0x4]
は、rbp - 0x4
の値を eax
にロードします。
これは main
の戻り値で、アドレス 2016a8
で暗黙的に初期化されています(mov dword ptr [rbp - 0x4], 0x0
)。
Cでは、main
が明示的に戻り値を指定しない場合、C標準により0が返されます。
アセンブリでは、mov dword ptr [rbp - 0x4], 0x0
(アドレス 2016a8
)は fahr
を初期化しており、戻り値ではありません。
実際の戻り値は、最後に mov eax, dword ptr [rbp - 0x4]
で設定されます。add rsp, 0x20
は32バイトのスタックスペースを解放します。pop rbp
は呼び出し元のベースポインタを復元します。ret
は呼び出し元に戻ります。
これでも未だ簡単ですよね?
明示的 vs 暗黙的戻り値
今回の例では、return 0;
を省略しましたが、C及びC++では、int main()
の場合にのみこれが許されます。
然し、return 0;
をCコードに追加すると、アセンブリ出力が少し変わります。
00000000002016a0 <main>:
2016a0: 55 push rbp
2016a1: 48 89 e5 mov rbp, rsp
2016a4: 48 83 ec 20 sub rsp, 0x20
2016a8: c7 45 fc 00 00 00 00 mov dword ptr [rbp - 0x4], 0x0
2016af: c7 45 f0 00 00 00 00 mov dword ptr [rbp - 0x10], 0x0
2016b6: c7 45 ec 2c 01 00 00 mov dword ptr [rbp - 0x14], 0x12c
2016bd: c7 45 e8 14 00 00 00 mov dword ptr [rbp - 0x18], 0x14
2016c4: 8b 45 f0 mov eax, dword ptr [rbp - 0x10]
2016c7: 89 45 f8 mov dword ptr [rbp - 0x8], eax
2016ca: 8b 45 f8 mov eax, dword ptr [rbp - 0x8]
2016cd: 3b 45 ec cmp eax, dword ptr [rbp - 0x14]
2016d0: 7f 36 jg 0x201708 <main+0x68>
2016d2: 8b 45 f8 mov eax, dword ptr [rbp - 0x8]
2016d5: 83 e8 20 sub eax, 0x20
2016d8: 6b c0 05 imul eax, eax, 0x5
2016db: b9 09 00 00 00 mov ecx, 0x9
2016e0: 99 cdq
2016e1: f7 f9 idiv ecx
2016e3: 89 45 f4 mov dword ptr [rbp - 0xc], eax
2016e6: 8b 75 f8 mov esi, dword ptr [rbp - 0x8]
2016e9: 8b 55 f4 mov edx, dword ptr [rbp - 0xc]
2016ec: 48 bf d8 04 20 00 00 00 00 00 movabs rdi, 0x2004d8
2016f6: b0 00 mov al, 0x0
2016f8: e8 b3 00 00 00 call 0x2017b0 <printf@plt>
2016fd: 8b 45 f8 mov eax, dword ptr [rbp - 0x8]
201700: 03 45 e8 add eax, dword ptr [rbp - 0x18]
201703: 89 45 f8 mov dword ptr [rbp - 0x8], eax
201706: eb c2 jmp 0x2016ca <main+0x2a>
201708: 31 c0 xor eax, eax
20170a: 48 83 c4 20 add rsp, 0x20
20170e: 5d pop rbp
20170f: c3 ret
先ず、デバッグ用のパディングがなくなっています。
変更された関連する行は以下の通りです:
201708: 8b 45 fc mov eax, dword ptr [rbp - 0x4]
代わりに、以下が表示されます:
201708: 31 c0 xor eax, eax
詰り、戻り値を検索する代わりに、バイナリは直接0の戻り値にアクセスします。
これにより、処理が高速化され、数バイト節約出来ます。
結論
全体として、それ程難しくありません。
一見すると威圧的に見えるかもしんが、何が起こっているかを理解すれば、アセンブリを学ぶのは直ぐに簡単になります。
アセンブリを理解すれば、全てのソフトウェアがオープンソースになります。
以上