機械語

提供: miniwiki
移動先:案内検索


ファイル:W65C816S Machine Code Monitor.jpeg
機械語モニタの出力表示例。逆アセンブルした機械語、プロセッサのレジスタとメモリダンプが表示されている。

機械語(きかいご)またはマシン語英語: Machine code、machine language)とは、コンピュータプロセッサが直接解釈実行可能な一連の命令群のデータそのもの(を、コンピュータ・プログラミング言語とみなしたもの)である。

概要

プロセッサは機械語で書かれたプログラムにしたがって処理を行っている。機械語でのプログラミングには、機械語とほぼ1対1に対応するニーモニックを用いたアセンブリ言語を使うのが一般的である。アセンブリ言語で書かれたプログラムを機械語に変換することをアセンブル(する)と言い、その処理系をアセンブラと言う。アセンブラによるアセンブルに対し、人力によるアセンブルをハンドアセンブルという。

直接、機械語を利用する理由は、以前は次のようなものであった。

  • アセンブラが存在しないか高価なため購入できない、クロスアセンブラであるため別のコンピュータが必要、など
  • コンピュータの性能が低いうえにBASICインタプリタしか備えていないなどで、必要な性能を得るため
  • コンパイラの研究が途上で高性能なコードが生成されないため

今日では、機械語を使わずとも十分なほどコンピュータは高性能になり、またコンパイラの研究も進んでよくできたコンパイラであれば場合によっては人より高性能なコードを生成するようになった。また、GNU Binutilsないし同様なライブラリがあることも多く、そういったユーティリティやライブラリを使うことで、アセンブラ・逆アセンブラを書いたりリバースエンジニアリングなども機械語に直接触れずできることも多い。そのため、機械語を直接扱うのは、そのようなユーティリティやライブラリが(まだ)無い新しいプロセッサの場合や対応していない新機能などを使う場合、プログラミング言語には馴染まない特殊な命令を扱う場合、trampoline[1]のようなテクニックが必要な場合、プロセッサのバグに当たった(等の可能性が疑われる)場合、何らかの理由でコアダンプを直接解析しなければならない場合、などに限られてきている。

機械語と互換性

機械語プログラムは命令セットその他の仕様が異なるプロセッサでは実行できない。同じ機械語プログラムを実行できることを互換性があるという。

たとえば、Pentium系列とPowerPC系列の双方で動くプログラムが存在しないのは、命令セットに互換性が無いからである。たとえ同じ系列だとしても、新しい世代のプロセッサのために作ったプログラムは古い世代のプロセッサでは動かないこともある。機械語プログラムがそのまま動くか否か、という互換性を「バイナリ互換性」といい、プロセッサの仕様だけでなく、コンピュータの他の部分の仕様やファームウェアオペレーティングシステムなども関わる。

ただし、注意深く機械語命令を使用することによって異なるアーキテクチャで動作するプログラムを書くことは不可能ではなく、Polyglotの極端な1ジャンルともいえる。PC-98とX68k両対応のブートセクタ[2]、記念すべき第1回IOCCCの入選作のひとつでmullenderによるPDP-11とVAX両対応プログラム[3]など。

機械語とアセンブリ言語

機械語を直接プログラミングに使うのは人間には負担が大きく、またコンパイラ等のコード生成でも(命令セットの設計にもよるが)見通しが良くないこともある。そういったこともあり、命令をオペコードではなくニモニックと呼ばれる英単語風のもので表現し、オペランドもレジスタ名などシンボリックに表現できる、アセンブリ言語を通じて扱うことが多く、C言語などの(処理系拡張による)インラインアセンブラ等でもアセンブリ言語の利用が一般的である。

基本的にはアセンブリ言語は機械語と1対1に対応するが、簡単なマクロなどを備えているものは多く、遅延スロットを利用するコードに自動的に変形するなどといった機能を持つものもある。また特殊な短縮形など(x86でAXがオペランドの場合など)について、機械語では違いがある場合をアセンブリ言語では明示的に指定できない場合もある。

アセンブリ言語で書いたコードを、手作業でアセンブルして機械語コードにする作業を「ハンドアセンブル」という。通常、メーカー等での新機種の開発などでは、旧機種の環境でクロス開発を行うわけで、わざわざ効率の悪い作業であるハンドアセンブル等を行うことは普通は無いが、1970年代のマイクロコンピュータや1980年代のパーソナルコンピュータでは、個人の場合、クロス開発のためのコンピュータ(当時はミニコンピュータなどが、メーカー等では使われていた)を別に持っているわけなどなく、またアセンブラも高額だったりすることも多く、ハンドアセンブルは一般的であった。

機械語と解析

アセンブラの逆を行う処理系が逆アセンブラである。残念ながらよくあることだが、開発の際の文書が失われたシステムを分析するために、プログラムを解析するしかないことがある。さらにソースコードも残っていないような場合には、機械語コードからなんとかして解析するしかないが、逆アセンブラで逆アセンブルすることで、基本的な手間は省けるかもしれない。しかし、各種のヒューリスティックによってサブルーチンや変数の名前などもある程度はそれらしく推測してくれるものもあるが、プログラムの意味を解析するのは人が行なう必要がある。

機械語プログラムの読み込み

ここでは、プログラム内蔵方式を前提とする。一般に電源投入ないし、いわゆるコールドリセットの直後にCPUが実行するコードはROMに置いておくか、CPUの動作に依らない方法でRAMに書き込まれている必要がある(ブート)。

オペレーティングシステム(OS)がブートされた後の、OS運用下では、ファイルシステムが存在するシステムの場合、補助記憶装置中のファイルシステムに、いわゆる「実行可能バイナリ」などと呼ばれる実行ファイルとして機械語プログラムが存在しており、それがファイルシステムから主記憶にロードされて実行される、というような形態が一般的である。なお、実行時に共有ライブラリ動的リンクするなど、近年はこの「ロードして実行する」という手続きが複雑になる傾向もあり、実行時コンパイル等が一般的になると主流の形態も変化するかもしれない。

ダンプリスト

ダンプリストそのものは機械語に限らず、コアダンプなど、バイナリをリスティング出力したものであり、オクテット単位を基本とするコンピュータ[4]では十六進法の2桁ずつで表現される。また1980年頃の「マイコン雑誌」の誌面に機械語プログラムが掲載される際の形態でもあった[5]

命令セットと命令フォーマットの設計によって、ダンプリストではほとんど意味不明なコードの場合もあれば、比較的読みやすいものもある。前述のようなハンドアセンブルやハンド逆アセンブルの経験者であれば、かなりその場で読めるような者もいる。そうでなくとも、デバッグ等で頻出するパターン(システムコールやサブルーチン呼出、プッシュ・ポップ等)は、経験で覚えてしまうことも多い。

構成

一般的な機械語プログラムは以下のような構成となっている。

命令部(オペレーションコード、あるいはオペコード
CPUに処理をさせるための命令の番号を記録している。
アドレス部(オペランド)
情報として利用するデータが格納されている、あるいは結果の記録先のレジスタやメモリアドレス、ジャンプ先などを示す、後述のイミディエイト値もこれに含めることもある。命令によって、個数や長さが異なる。オペランドの数について、0アドレス方式、1アドレス方式、2アドレス方式、3アドレス方式がある。0アドレス方式はオペレーションコードだけで、オペランドは存在しない。
イミディエイト値
オペランドの一部に含めることもある。演算に使用する整数値などのデータが命令に引き続いて置かれているもの。即値とも。
データ部
データ部は実行されない部分である。プログラムで使用するデータのうち前述のイミディエイト値に収まらないもの、文字列リテラルなどのような定数データ、グローバル変数(機械語やアセンブリ言語プログラミングの用語ではワークエリア)のためのヒープ領域やローカル変数のためのスタック領域など。通常ある程度まとめて置かれる。通例、命令として解釈することはできないが、強制的にデータ部にジャンプさせ、命令部として解釈させることで、仮にデータ部に機械語相当の命令データが配置されていた場合はそれらを実行することができる環境も存在する。これを悪用して、リターンアドレスの書き換えなどにより不正なコードを実行させてしまうような悪意のあるソフトウェアからコンピュータを保護するため、データ実行防止(DEP)と呼ばれる機能を備えている環境もある[6][7]

以上の各部分に具体的に何ビットずつ割り振って、どういう順番に並べるか、という形式(フォーマット)のことを機械語フォーマットなどと言う。アーキテクチャにより機械語フォーマットはまちまちだが、1命令を構成するデータ長が固定の「固定長」式と、命令やオペランドの種類により変化する「可変長」式に大別される。可変長の場合、機械語命令の種類によってアドレス部やデータ部、そして中には命令部までも長さが変わる。このため、読み込み位置が1バイトずれれば機械語の命令はそれ以降のすべての命令が正しく読み込まれず意味を失うため、そういった機械語フォーマットのバイナリを対象とする逆アセンブラは工夫を要する[8]。またメモリが限られるシステムでは本来の命令の途中に飛び込み別の意味に使うというトリック的な手法もある。

CPUによる仕様の差異

上記類似点の範囲でのCPUごとの機械語の仕様の差異には、以下のようなものが挙げられる。

  • CPUが理解できる命令の種類や数が異なる(CISCRISCVLIW
  • 命令の長さが異なる(CISCとRISCとでは長さが異なることがある。また、同じアーキテクチャでも、命令のビット数の違いも影響する)
  • 命令部の命令番号が一致しない
  • 同じ処理を行う命令でも処理結果が異なる
  • 演算方法が異なる(レジスタ - レジスタ間演算やメモリ - レジスタ間演算の違い。RISCでは後者の演算ができない)
  • データの記録方法が異なる(エンディアンアラインメントの相違)
  • 実行形式のバイナリファイルの記録形式が異なる(PECOFFELFなど)

脚注

  1. http://catb.org/jargon/html/T/trampoline.html
  2. 電脳インストーラ2
  3. http://www.ioccc.org/years.html#1984_mullender ソースコード参照。なお後の回ではコンテストのルールが変更されこのようなプログラムは禁止されている。
  4. たいていはオクテット単位だが、CASLのCOMETが16ビット単位のように、そうでないものもある。
  5. 「絶対にBASICプログラムという形態で載せる」という掟のあった、『マイコンBASICマガジン』(ベーマガ)を除く。このためベーマガでは、DATA文のデータから、BASICプログラムで機械語コードを書き込むようなプログラムを掲載していた。
  6. データ実行防止
  7. IPA ISEC セキュア・プログラミング講座:C/C++言語編 第10章 著名な脆弱性対策:バッファオーバーフロー: #5 運用環境における防御
  8. 全て機械任せで良い結果を得ようとするよりも、人による補助をうまく取り入れられるようにするほうが良い場合もある。

関連項目