Wikipedia
Wikipedia
ウィキペディア機械語の記事があります。

概要 編集

機械語は、コンピュータが直接実行できるプログラムの形式であり、通常は2進数で表されます。コンピュータのプロセッサは、機械語プログラムの命令を一つずつ読み込み、解釈して実行します。機械語は、高水準のプログラム言語(例えばC言語など)と比較すると、非常に低レベルの言語であり、直接ハードウェアに対するアクセスが可能です。

機械語は、一般的にアセンブリ言語という人間が理解しやすい形式に変換されます。アセンブリ言語は、機械語と1対1に対応する命令を持つため、プログラマにとっては理解しやすいものになっています。

機械語は、コンピュータのプロセッサが理解できる唯一のプログラム形式であるため、コンピュータの動作を理解する上で非常に重要な概念です。また、機械語を直接書くことで、コンピュータの性能を最大限に引き出すことができます。

ソースコードからどんな機械語が生成されるか 編集

フィボナッチ数を返す関数(C言語)
int fibo(int n) {
    if (n == 0 || n == 1)
      return n;
    return fibo(n-1) + fibo(n-2);
}

32bitARMプロセッサーの例 編集

コンパイラーによって生成されたコード
fibo.o:     file format elf32-littlearm


Disassembly of section .text:

00000000 <fibo>:
int fibo(int n) {
   0:   e92d4830        push    {r4, r5, fp, lr}
   4:   e28db008        add     fp, sp, #8
    if (n == 0 || n == 1)
   8:   e3500002        cmp     r0, #2
   c:   e1a04000        mov     r4, r0
      return n;
    return fibo(n-1) + fibo(n-2);
}
  10:   31a00004        movcc   r0, r4
  14:   38bd4830        popcc   {r4, r5, fp, lr}
  18:   312fff1e        bxcc    lr
    return fibo(n-1) + fibo(n-2);
  1c:   e2440001        sub     r0, r4, #1
  20:   ebfffffe        bl      0 <fibo>
  24:   e1a05000        mov     r5, r0
  28:   e2440002        sub     r0, r4, #2
  2c:   ebfffffe        bl      0 <fibo>
  30:   e0800005        add     r0, r0, r5
}
  34:   e8bd4830        pop     {r4, r5, fp, lr}
  38:   e12fff1e        bx      lr

このコードは32bitARMプロセッサをターゲットとしたもので、すべての命令が32ビット長なのでアセンブラーの初学者向きです。 また、ARMアーキテクチャは多くのスマートフォンやタブレットで採用されていたり、組込み用途での採用も多いので最も普及しているコンピュータ・アーキテクチャの1つです。

  10:   31a00004        movcc   r0, r4
10:がアドレス、31a00004が機械語命令の16進数表現です。
movcc は Move on キャリークリアーで、キャリーフラッグ(桁上りがあった時にセットされる)かセットされたときだけ r4レジスターの値を r0レジスターに代入します[1]

Thumb命令の例 編集

ARMプロセッサはThumbと呼ばれるコード効率の向上を意図した16ビット長のThumb命令モードを持っています。

コンパイラーによって生成されたコード
fibo.o:     file format elf32-littlearm


Disassembly of section .text:

00000000 <fibo>:
int fibo(int n) {
   0:   b5b0            push    {r4, r5, r7, lr}
   2:   af02            add     r7, sp, #8
   4:   4604            mov     r4, r0
    if (n == 0 || n == 1)
   6:   2802            cmp     r0, #2
   8:   d201            bcs.n   e <fibo+0xe>
      return n;
    return fibo(n-1) + fibo(n-2);
}
   a:   4620            mov     r0, r4
   c:   bdb0            pop     {r4, r5, r7, pc}
    return fibo(n-1) + fibo(n-2);
   e:   1e60            subs    r0, r4, #1
  10:   f7ff fffe       bl      0 <fibo>
  14:   4605            mov     r5, r0
  16:   1ea0            subs    r0, r4, #2
  18:   f7ff fffe       bl      0 <fibo>
  1c:   4428            add     r0, r5
}
  1e:   bdb0            pop     {r4, r5, r7, pc}
  10:   f7ff fffe       bl      0 <fibo>
bl は Brunch with link で、次の命令のアドレス(ここで言えば 14:)を lrレジスター(リンクレジスター)に保存し、指定されたアドレス(この場合は <fibo>; 関数自身なので再帰です)にジャンプします。多くのプロセッサーでは call と呼ばれスタックに次の命令のアドレスを積んだ後に関数やサプルーチンにジャンプしますが、ARMでは lr が戻り番地を保存される目的に使われ、リーフプロシージャー(それ自身は関数やサブルーチンを呼び出さないプロシージャー)の効率を向上させています。
Thumb命令は概ね命令長は16ビットで、長いオペランドが必要な命令(この場合は bl)だけが追加のオペランドを持ちます。

64bitARMプロセッサーの例 編集

ARMアーキテクチャーは、ARMv8-Aから64ビットモードアーキテクチャーAArch64を採用してます。 AArch64は、32本の64ビットレジスター(うち1本はスタックポインター兼ゼロレジスタ、1本は戻り番地を保持するリンクレジスタ)を持ち、Xnnレジスターは64ビットレジスターのnn本目、WnnはXnnレジスターの下位32ビットです。

コンパイラーによって生成されたコード
fibo.o:     file format elf64-littleaarch64


Disassembly of section .text:

0000000000000000 <fibo>:
int fibo(int n) {
   0:   a9be7bfd        stp     x29, x30, [sp, #-32]!
   4:   a9014ff4        stp     x20, x19, [sp, #16]
   8:   910003fd        mov     x29, sp
   c:   2a1f03f3        mov     w19, wzr
    if (n == 0 || n == 1)
  10:   71000814        subs    w20, w0, #0x2
  14:   540000e3        b.cc    30 <fibo+0x30>  // b.lo, b.ul, b.last
      return n;
    return fibo(n-1) + fibo(n-2);
  18:   51000400        sub     w0, w0, #0x1
  1c:   94000000        bl      0 <fibo>
  20:   2a0003e8        mov     w8, w0
  24:   2a1403e0        mov     w0, w20
  28:   0b130113        add     w19, w8, w19
  2c:   17fffff9        b       10 <fibo+0x10>
}
  30:   0b130000        add     w0, w0, w19
  34:   a9414ff4        ldp     x20, x19, [sp, #16]
  38:   a8c27bfd        ldp     x29, x30, [sp], #32
  3c:   d65f03c0        ret

wzrはゼロレジスターを32bitで参照しています、spはスタックポインターでアドレス演算の文脈と左辺値の文脈ではスタックポインター、右辺値の場合はゼロレジスターになりレジスタインデックス(31番)を共有しています。 aarch64 では32bitARMと違って全ての命令に条件フラッグ参照が着くわけではないので、どちらかというと Thumb に似ていますが最小命令サイズは32bitです。

amd64プロセッサーの例 編集

同じコードをamd64向けにコンパイル
fibo.o:     file format elf64-x86-64-freebsd


Disassembly of section .text:

0000000000000000 <fibo>:
int fibo(int n) {
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   41 56                   push   %r14
   6:   53                      push   %rbx
   7:   89 fb                   mov    %edi,%ebx
   9:   45 31 f6                xor    %r14d,%r14d
    if (n == 0 || n == 1)
   c:   83 ff 02                cmp    $0x2,%edi
   f:   72 16                   jb     27 <fibo+0x27>
  11:   45 31 f6                xor    %r14d,%r14d
      return n;
    return fibo(n-1) + fibo(n-2);
  14:   8d 7b ff                lea    -0x1(%rbx),%edi
  17:   e8 00 00 00 00          call   1c <fibo+0x1c>
  1c:   83 c3 fe                add    $0xfffffffe,%ebx
  1f:   41 01 c6                add    %eax,%r14d
    if (n == 0 || n == 1)
  22:   83 fb 01                cmp    $0x1,%ebx
  25:   77 ed                   ja     14 <fibo+0x14>
    return fibo(n-1) + fibo(n-2);
  27:   41 01 de                add    %ebx,%r14d
}
  2a:   44 89 f0                mov    %r14d,%eax
  2d:   5b                      pop    %rbx
  2e:   41 5e                   pop    %r14
  30:   5d                      pop    %rbp
  31:   c3                      ret

amd64(X86-64とも)の命令は最小単位は1バイトで、バイト数あたりの操作が多いのが特徴です。 逆アッセンブルされたコードを読む限り不便は感じませんが、ハンドディスアッセンブルする場合は1バイトずれるとまるで違った意味になるのが厄介で、プロセッサーの中でも数命令先の命令を読み込み実行効率を上げる為に命令の切れ目を探すことが性能向上のボトルネックになっています(ARMなら次の命令は4バイト先と決まっているので深い先読みが相対的に容易)。

  14:   8d 7b ff                lea    -0x1(%rbx),%edi
14:がアドレス、8d 7b ffが機械語命令の16進数表現です。
leaは Load effective address で、通常は -0x1(%rbx)は rbx レジスタの値から1引いたアドレスの値へのアクセスを表しますが、LEA ではその時にアドレスバスに出る値(Effective address)を第二オペランド(この場合は edi レジスタ)にセットします。内部的にはアドレス計算器を数値演算に転用しています。
またコードにはARMにはあった再帰のコードがなくループに置き換えられています。

x86(32ビット)のコード 編集

x86(32ビット)のコード
fibo.o:     file format elf32-i386-freebsd


Disassembly of section .text:

00000000 <fibo>:
int fibo(int n) {
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   57                      push   %edi
   4:   56                      push   %esi
   5:   8b 7d 08                mov    0x8(%ebp),%edi
   8:   31 f6                   xor    %esi,%esi
    if (n == 0 || n == 1)
   a:   83 ff 02                cmp    $0x2,%edi
   d:   72 18                   jb     27 <fibo+0x27>
   f:   31 f6                   xor    %esi,%esi
      return n;
    return fibo(n-1) + fibo(n-2);
  11:   8d 47 ff                lea    -0x1(%edi),%eax
  14:   50                      push   %eax
  15:   e8 fc ff ff ff          call   16 <fibo+0x16>
  1a:   83 c4 04                add    $0x4,%esp
  1d:   83 c7 fe                add    $0xfffffffe,%edi
  20:   01 c6                   add    %eax,%esi
    if (n == 0 || n == 1)
  22:   83 ff 01                cmp    $0x1,%edi
  25:   77 ea                   ja     11 <fibo+0x11>
    return fibo(n-1) + fibo(n-2);
  27:   01 fe                   add    %edi,%esi
}
  29:   89 f0                   mov    %esi,%eax
  2b:   5e                      pop    %esi
  2c:   5f                      pop    %edi
  2d:   5d                      pop    %ebp
  2e:   c3                      ret

amd64は、x86のアーキテクチャーを拡張する形で命令セットを設計しているので、両者は似通っていますが相応の差異があります。

amd64
   1:   48 89 e5                mov    %rsp,%rbp
x86
   1:   89 e5                   mov    %esp,%ebp
amd64では RSPレジスターの内容をRBPレジスタにコピーしており 48 が前置され3バイト。
x86では ESPレジスターの内容をEBPレジスタにコピーしており 48 がなく、2バイトです。
RSPとRBPは64ビットレジスター、ESPとEBPは32ビットのレジスターです。
48はREXプリフィックスの一種で、「REX.w=オペランドサイズを64ビットにする。」を意味します。
x86/amd64はこの様なプリフィックスがいくつもあり、1つの命令にいくつもプリフィックスを前置する場合まであります。

まとめ 編集

  • 異なるプロセッサーでは、同じ高級言語のコードが全く違う機械語命令列にコンパイルされる。
  • 同じプロセッサーでも命令モードによって、同じ高級言語のコードが全く違う機械語命令列にコンパイルされる。
  • 再帰などのプログラム構造もコンパイラーによって(意味解析され)等価のより速度的に(あるいはメモリーフットプリント的に)優れたコードに置き換えられる(事がある)。
機械語 = バイナリーデータ?

機械語はバイナリーデータですが、全てのバイナリーデータが機械語ではありません。

例えば、画像データや映像データはバイナリーデータですが機械語ではありません。

実行ファイルもバイナリーデータですが、オペレーションシステムが「どの様に配置するのか?」「どの位置から実行するのか?」あるいは「初期化済みのデータ領域の値」など機械語以外の付帯的な情報(一般にヘッダーと呼ばれます)を重層的に持っているので、機械語を含んでいますが機械語そのものではありません。

機械語は、アセンブラのニーモニックに一対一で対応するコードと理解するとわかりやすいと思います。


用語集 編集

機械語に関する用語集

  • オペコード(Opcode) - 機械語命令の操作コード。特定の動作を実行するための識別子。
  • オペランド(Operand) - オペコードによって指定された操作対象。演算の対象となる数値やアドレス。
  • レジスタ(Register) - CPU内にある高速なメモリ領域で、演算に使用されるデータやアドレスを格納するために使用される。
  • フラグ(Flag) - CPUの状態を示すビットで、演算の結果を表す。フラグは、CPU内部で条件分岐を行うために使用される。
  • メモリアドレス(Memory Address) - メモリ内の特定の場所を示す番号。機械語のオペランドとして使用されることが多い。
  • メモリマップドI/O(Memory-mapped I/O) - I/Oデバイスを制御するために、メモリアドレスを使用する方法。
  • ファイルI/O(File I/O) - ディスクやネットワーク上のファイルを操作するために使用される命令。
  • 命令ポインタ(Instruction Pointer) - CPUが次に実行する機械語命令のアドレスを示すレジスタ。
  • サブルーチン(Subroutine) - 他の部分から呼び出される、独立した機械語のブロック。
  • スタック(Stack) - プログラム内で一時的なデータを格納するためのメモリ領域。
  • エンディアン(Endian) - データの並び順を示す方法。リトルエンディアンは、最下位バイトから順にデータを配置する方式。ビッグエンディアンは、最上位バイトから順にデータを配置する方式。
  • マシンサイクル(Machine Cycle) - CPUが一つの命令を実行するために必要なサイクル数。
  • 命令セット(Instruction Set) - 特定のCPUが実行できる機械語命令の集合。
  • アセンブラ(Assembler) - アセンブリ言語を機械語に変換するプログラム。
  • リンカ(Linker) - 複数のオブジェクトファイルを結合して、実行可能なプログラムを生成するプログラム。
  • デバッガ(Debugger) - プログラムの実行中に、機械語命令やレジスタの値を監視し、プログラムの動作を解析するツール。
  • トレース(Trace) - プログラムの実行中に、実行された命令やメモリアクセスなどの履歴を保存すること。
  • ブレークポイント(Breakpoint) - プログラムの実行中に、特定の命令の実行を一時停止し、デバッグのために命令の内容やレジスタの値を確認するために使用されるポイント。
  • クロスコンパイラ(Cross-compiler) - 特定のCPU向けに、異なるプラットフォームでコンパイルするためのコンパイラ。
  • リバースエンジニアリング(Reverse Engineering) - プログラムやハードウェアの動作を解析して、仕様や設計図を作成するプロセス。
  • コンパイル(Compile) - 高水準言語で書かれたプログラムを、機械語に変換するプロセス。
  • リンク(Link) - コンパイルされた複数のオブジェクトファイルを、実行可能なプログラムに結合するプロセス。
  • アセンブル(Assemble) - アセンブリ言語で書かれたプログラムを、機械語に変換するプロセス。
  • ローダ(Loader) - プログラムをメモリに読み込み、実行可能な状態にするプログラム。
  • バイトコード(Bytecode) - 実行環境に依存しない、仮想マシン上で実行される機械語。
  • オーバーフロー(Overflow) - データ型の最大値を超える演算が行われた場合に発生する、予期しない結果のこと。
  • セグメンテーション違反(Segmentation Fault) - プログラムがメモリの範囲外をアクセスしようとした場合に発生するエラー。
  • プロセス(Process) - プログラムの実行中に割り当てられる、メモリやレジスタ、実行状態などのリソースの集合。
  • マルチプロセッシング(Multiprocessing) - 複数のプロセッサを使用して、プログラムを並列に処理する方式。


脚註 編集

  1. ^ ARMアーキテクチャでは、多くの命令でキャリーなどのコンディションコードによって実行する・しないを制御できるのが大きな特徴で、他のアーキテクチャではジャンプ命令以外でコンディションコードによって実行する・しないを制御できるのは稀です(他にはIA-64がプレディケート可能です)。