機械語とは何か?ISAとの関係と構成要素・エンディアン・実行の流れを徹底解説

機械語とは — 概要

機械語(きかいご、machine code)は、コンピュータの中央処理装置(CPU)が直接実行できる命令の最も低レベルな表現です。通常はバイト列(0 と 1 の並び=バイナ)として記録され、CPU の命令デコーダが読み取って実行することで処理が進みます。「機械語」はアセンブリ言語や高水準言語とは異なり、CPU の命令セット(Instruction Set Architecture:ISA)に依存するため、CPU ごとに異なります。

機械語の構成要素

  • オペコード(opcode):命令の種類を表すビット列。例:加算、ロード、ストア、分岐など。
  • オペランド:命令が処理する対象(レジスタ、メモリアドレス、即値など)。オペコードに続いてエンコーディングされる。
  • アドレッシングモード:オペランドの指定方法。例えばレジスタ直接、即値、ベース+オフセット、インデックスなど。
  • 命令長・エンコーディング:命令を何バイトで表すか(可変長/固定長)やビットフィールドの配置。

ISA(命令セットアーキテクチャ)との関係

機械語は ISA の仕様に従って定義されます。ISA は命令の意味(セマンティクス)と機械語レベルでの表現(エンコーディング)を定めます。有名な ISA に x86、ARM(AArch32/AArch64)、RISC-V、PowerPC などがあります。例えば:

  • x86:可変長命令を持つ複雑命令セット(CISC)の代表。命令長は 1〜15 バイトなど可変で、エンコーディングが複雑。
  • ARM(AArch64):通常は固定長(32 ビット)命令。Thumb 系では 16/32 ビットの混在がある。
  • RISC-V:基本は固定 32 ビット命令で、圧縮拡張により 16 ビット命令も利用可能。

アセンブリ言語やバイトコードとの違い

しばしば混同される用語としてアセンブリ言語(assembly language)とバイトコード(bytecode)があります。アセンブリは機械語の人間可読なニーモニック表現(例:MOV, ADD)であり、アセンブラで機械語に翻訳されます。バイトコードは Java の .class や .NET の MSIL のように仮想マシン(VM)向けに設計された中間表現であり、必ずしも実機の CPU が直接実行するものではありません(JIT によって機械語へ変換されることが多い)。

バイナリ表現とエンディアン

機械語の命令や即値はバイト列としてメモリやストレージに保存されます。この際に生じるのがエンディアン(バイト順序)問題です。代表的にはリトルエンディアン(下位バイトが先)とビッグエンディアン(上位バイトが先)があります。CPU やプラットフォームごとに扱いが異なるため、バイナリを扱う際は注意が必要です。

命令長の違いと設計思想(CISC vs RISC)

CISC(Complex Instruction Set Computer)は多様で高機能な命令を持ち、アセンブリの命令数を減らせる利点がありますが、エンコーディングやデコーダが複雑になります。x86 系が代表例です。一方 RISC(Reduced Instruction Set Computer)は命令を単純化・固定長化してデコーディングを容易にし、パイプライン処理や高速クロックで高性能を引き出すことを狙います。RISC-V や ARM(AArch64)はこの思想に近い設計です。

マイクロコードと実際の実行

一部の複雑な命令を持つ CPU(特に CISC)では、機械語命令が内部的にさらに低レベルな「マイクロ命令」に分解されて実行される場合があります(マイクロコード)。これは動作の柔軟性を高める一方で、設計の複雑化や性能面のトレードオフをもたらします。

実行の流れ(大まかなプロセス)

  • プログラムは機械語としてメモリにロードされる(実行ファイル:ELF、PE、Mach-O など)。
  • CPU は命令ポインタ(プログラムカウンタ)で次の命令のアドレスを指し、フェッチする。
  • 命令をデコードして、演算ユニットやメモリアクセス指示を出す。
  • 必要ならオペランドを読み込み、演算を実行し、結果を書き戻す。
  • 分岐や割り込みがあればプログラムカウンタが更新され、次の命令へ進む。

実務で知っておくべきポイント

  • 可搬性の欠如:機械語は CPU に依存するため、異なるアーキテクチャ間でバイナリをそのまま実行できない。
  • 実行ファイルフォーマット:ELF(Linux)、PE(Windows)、Mach-O(macOS/iOS)などにはヘッダ、セクション、シンボル、リロケーション情報が含まれる。
  • 動的リンク/位置非依存コード:共有ライブラリや ASLR(アドレス空間配置のランダム化)などのために再配置情報や PIC(Position Independent Code)が使われる。
  • 自己書き換えコード・JIT:自己書き換えや JIT コンパイラは実行時に機械語を生成・変更するが、現代 OS では実行/書込みのページ属性(W^X)により制限されることが多い。

セキュリティ、解析、逆アセンブル

機械語は攻撃者とディフェンダの両面で重要です。バッファオーバーフローやコードインジェクションは機械語レベルでの制御フロー改変を狙います。逆に、リバースエンジニアリングやマルウェア解析では機械語をアセンブリに戻して解析します。代表的なツールには objdump、readelf、hexdump、IDA Pro、Ghidra、radare2 などがあります。

具体例(x86 の簡単な例)

例として x86(32/64 ビット)での単純な機械語バイト列:

  • B8 01 00 00 00 — これは “mov eax, 1”(EAX レジスタに即値 1 を代入)を表します。ここで命令バイト B8 の後にリトルエンディアンで 32 ビットの即値 01 00 00 00 が続きます。

このように、バイト列を読むことで命令の意味を復元できますが、x86 のような可変長命令ではデコーディングがやや複雑です。

教育的な簡易サンプル(仮想 CPU)

理解のために単純化した仮想 CPU の例:

  • 0x01: LOAD A, imm8
  • 0x02: ADD A, imm8
  • 0x03: STORE A, addr8

バイト列 01 05 02 03 03 10 は「A に 5 をロード、A に 3 を足す、A をアドレス 0x10 にストア」といった具合に解釈できます(あくまで教育目的の例)。

近年のトピック:JIT、仮想化、スペキュレーティブ実行の脆弱性

JIT(Just-In-Time)コンパイラはランタイムで機械語を生成し、性能を向上させます。また、仮想化技術ではゲスト OS の機械語命令がホストとどうやって安全に実行されるかが問題になります。近年はスペキュレーティブ実行(投機実行)を巡る脆弱性(Spectre / Meltdown)により、CPU の実行モデルと機械語の影響がセキュリティ上の大問題になりました。

まとめ

機械語はコンピュータが直接理解し実行する命令列であり、CPU の ISA に強く依存します。アセンブリは機械語の人間可読版で、バイトコードや高水準言語とは役割が異なります。実務ではエンコーディング、実行ファイルフォーマット、エンディアン、セキュリティ、解析ツールなどを理解しておくことが重要です。

参考文献