現在のブログ
ゲーム開発ブログ (2025年~) Gamedev Blog (2025~)
レガシーブログ
テクノロジーブログ (2018~2024年) リリースノート (2023~2025年) MeatBSD (2024年)
 
        【プログラミング】C言語で使ってアセンブリ言語の勉強する方法
アセンブリ言語を学ぶ方法は沢山ありますが、少なくとも1つのアセンブリ方言を学ぶ事をお勧めします。
1つの方言を習得すると、他のアセンブリ言語も学び易くなり、コンピュータがどの様に動作するかを深く理解出来ます。
アセンブリを読めないと、コンピュータの仕組みを本当に理解するのは難しいです。
通常は、MIPSやRISC-Vアセンブリから始める事をお勧めします。
これらは非常にシンプルだからです。
然し、今回は貴方がIntel又はAMDプロセッサを使用していると仮定して、x64アセンブリを見ていきます。
C言語からアセンブリを学ぶワークフロー
- 簡単なCプログラムを書く。
- cc -o program program.cでコンパイルする。
- objdump -d -Mintel program | lessを使ってバイナリを逆アセンブルする。
- main関数を探し、アセンブリ命令を分析する。
- 各アセンブリ命令を対応するCコードにマッピングする。
最適化なし(デフォルトで -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進数で表現されている事に気付くでしょう。
見逃した人の為に、ビット演算について詳しく説明した記事をこちらに書いています。
アセンブリの基礎
アセンブリ言語は、マシン命令に直接対応する低レベル言語です。
主な概念は以下の通りです:
- レジスタ: CPU内の小さくて高速なストレージ(例: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の戻り値にアクセスします。
これにより、処理が高速化され、数バイト節約出来ます。
結論
全体として、それ程難しくありません。
一見すると威圧的に見えるかもしんが、何が起こっているかを理解すれば、アセンブリを学ぶのは直ぐに簡単になります。
アセンブリを理解すれば、全てのソフトウェアがオープンソースになります。
以上