アセンブリからマシンコードへの移行方法(コード生成)


16

コードを機械コードにアセンブルするステップを視覚化する簡単な方法はありますか?

たとえば、メモ帳でバイナリファイルを開くと、マシンコードのテキスト形式の表現が表示されます。私はあなたが見る各バイト(シンボル)がそのバイナリ値に対応するASCII文字であると仮定しますか?

しかし、アセンブリからバイナリにどのように移行するのでしょうか。舞台裏で何が起こっているのでしょうか??

回答:


28

命令セットのドキュメントを見ると、各命令のpicマイクロコントローラーから次のようなエントリが見つかります。

addlw命令の例

「エンコード」行は、その命令がバイナリでどのように見えるかを示します。この場合、常に5で始まり、次にドントケアビット(1または0のいずれか)が追加され、「k」は追加するリテラルを表します。

最初の数ビットは「オペコード」と呼ばれ、各命令に固有です。CPUは基本的にオペコードを見てどの命令であるかを確認し、「k」を追加する数値としてデコードすることを認識します。

面倒ですが、エンコードとデコードはそれほど難しくありません。私は試験で手でそれをしなければならなかった学部クラスを持っていました。

実際に完全な実行可能ファイルを作成するには、オペレーティングシステムに応じて、メモリの割り当て、ブランチオフセットの計算、ELFなどの形式への配置などを行う必要があります。


10

アセンブリオペコードの大部分は、基礎となるマシン命令と1対1で対応しています。したがって、必要なのは、アセンブリ言語で各オペコードを識別し、対応するマシン命令にマップし、対応するパラメーター(存在する場合)とともにマシン命令をファイルに書き込むことだけです。次に、ソースファイル内の追加のオペコードごとにプロセスを繰り返します。

もちろん、オペレーティングシステム上で適切にロードして実行する実行可能ファイルを作成するには、それ以上のものが必要です。また、ほとんどのアセンブラーには、オペコードからマシン命令(マクロなど)への単純なマッピングを超える追加機能があります。


7

最初に必要なのは、このファイルのようなものです。これは、NASMアセンブラーが使用するx86プロセッサー用の命令データベースです(実際に命令を変換する部分ではありませんが、作成に役立ちました)。データベースから任意の行を選択できます。

ADD   rm32,imm8    [mi:    hle o32 83 /0 ib,s]      386,LOCK

これが意味することは、それが指示を説明するということADDです。この命令には複数のバリアントがありますが、ここで説明している特定のバリアントは、32ビットのレジスタまたはメモリアドレスを取り、8ビットの値(つまり、命令に直接含まれる定数)を追加するバリアントです。このバージョンを使用するアセンブリ命令の例は次のとおりです。

add eax, 42

次に、テキスト入力を取得して、個々の命令とオペランドに解析する必要があります。上記の命令の場合、これはおそらく、命令ADD、およびオペランドの配列(レジスタEAXおよび値への参照)を含む構造になります42。この構造ができたら、命令データベースを実行して、命令名とオペランドのタイプの両方に一致する行を見つけます。一致するものが見つからない場合、それはユーザーに提示する必要があるエラーです(「オペコードとオペランドの違法な組み合わせ」などが通常のテキストです)。

データベースから行を取得したら、3番目の列を確認します。この命令の場合は次のとおりです。

[mi:    hle o32 83 /0 ib,s] 

これは、必要なマシンコード命令を生成する方法を説明する一連の命令です。

  • miオペランドのdescriptiuonです:1 modr/m(私たちが追加する必要がありますを意味している(レジスタまたはメモリ)のオペランドmodr/m後ほどに来る命令の末尾にバイトを)および(意志1つの即時命令命令の説明で使用されます)。
  • 次はhle。これは、「ロック」プレフィックスの処理方法を識別します。「ロック」を使用していないため、無視します。
  • 次ですo32。これは、16ビット出力形式のコードをアセンブルする場合、命令にオペランドサイズのオーバーライドプレフィックスが必要であることを示しています。16ビットの出力を作成していた場合、ここで接頭辞を作成します(0x66)が、そうでないと仮定して続行します。
  • 次です83。これは16進数のリテラルバイトです。出力します。
  • 次です/0。これはmodr / mのバイテムで必要となるいくつかの追加ビットを指定し、それを生成させます。このmodr/mバイトは、レジスタまたは間接メモリ参照をエンコードするために使用されます。このような単一のオペランド、レジスタがあります。レジスタには番号があり、これは別のデータファイルで指定されています

    eax     REG_EAX         reg32           0
  • reg32元のデータベースからの命令に必要なサイズと一致することを確認します(一致します)。0レジスタの数です。modr/mバイトは、このようなものに見えるプロセッサによって指定されたデータ構造であります:

     (most significant bit)
     2 bits       mod    - 00 => indirect, e.g. [eax]
                           01 => indirect plus byte offset
                           10 => indirect plus word offset
                           11 => register
     3 bits       reg    - identifies register
     3 bits       rm     - identifies second register or additional data
     (least significant bit)
  • レジスタを使用しているため、modフィールドは0b11です。

  • regフィールドには、私たちが使用しているレジスタの数です0b000
  • この命令にはレジスタが1つしかないため、rmフィールドに何かを入力する必要があります。それ/0がで指定された追加データの目的であるため、rmフィールドにそれを置き0b000ます。
  • modr/mしたがって0b11000000、バイトはまたはです0xC0。これを出力します。
  • 次ですib,s。これは、符号付き即値バイトを指定します。オペランドを見て、使用可能な即値があることに注意してください。これを符号付きバイトに変換して出力します(42=> 0x2A)。

したがって、完成した完全な命令は次のとおり0x83 0xC0 0x2Aです。出力モジュールに送信します。また、バイトはメモリ参照を構成しないことに注意してください(出力モジュールは、参照するかどうかを知る必要がある場合があります)。

すべての指示について繰り返します。ラベルを追跡して、参照時に挿入する内容を把握します。オブジェクトファイル出力モジュールに渡されるマクロおよびディレクティブの機能を追加します。そして、これは基本的にアセンブラーの仕組みです。


1
ありがとうございました。偉大な説明は、それが0b11000000 = 0xC0のためではなく、「0x83の0xB0 0x2A」よりも「0x83の0xC0の0x2A」であってはならない
カムラン

@Kamran-... $ cat > test.asm bits 32 add eax,42 $ nasm -f bin test.asm -o test.bin $ od -t x1 test.bin 0000000 83 c0 2a 0000003ええ、あなたはまったく正しいです。:)
ジュール

2

実際には、アセンブラは通常、バイナリ実行可能ファイル直接生成せず、オブジェクトファイル(後でリンカに供給する)を生成します。ただし、例外があります(一部のアセンブラを使用して、バイナリ実行可能ファイルを直接生成できます。これらは一般的ではありません)。

まず、多くのアセンブラーが今日のフリーソフトウェアプログラムであることに注意してください。したがって、GNU asbinutilsの一部)およびnasmのソースコードをコンピューターにダウンロードしてコンパイルします。次に、ソースコードを調べます。ところで、私はその目的のためにLinuxを使用することをお勧めします(これは非常に開発者に優しい、フリーソフトウェアに優しいOSです)。

アセンブラによって生成されたオブジェクトファイルには、特にコードセグメント再配置命令が含まれています。オペレーティングシステムに依存する、十分に文書化されたファイル形式で構成されています。Linuxでは、その形式(オブジェクトファイル、共有ライブラリ、コアダンプ、および実行可能ファイルに使用)はELFです。そのオブジェクトファイルは、後でリンカーに入力されます(最終的に実行可能ファイルが生成されます)。再配置はABIによって指定されます(例x86-64 ABI)。詳細については、Levineの本Linkers and Loadersをお読みください。

このようなオブジェクトファイルのコードセグメントには、穴のあるマシンコードが含まれています(再配置情報を使用して、リンカによって埋められます)。アセンブラーによって生成される(再配置可能な)マシンコードは、明らかに命令セットアーキテクチャに固有のものです。x86またはx86-64の(ほとんどのラップトップまたはデスクトッププロセッサで使用される)のISAはその詳細にひどく複雑です。しかし、y86またはy86-64と呼ばれる単純化されたサブセットは、教育目的で発明されました。それらのスライドを読んでください。この質問に対する他の回答もその少しを説明しています。コンピュータアーキテクチャに関する優れたを読むことをお勧めします。

ほとんどのアセンブラーは2つのパスで作業しています。2つ目のパスは、再配置を実行するか、最初のパスの出力の一部を修正します。現在、通常の解析手法を使用しています(おそらく、The Dragon Bookを読んでください)。

OS カーネルによる実行可能ファイルの起動方法(execveLinuxでのシステムコールの動作など)は、別の(そして複雑な)問題です。これは通常、いくつかの設定仮想アドレス空間(内プロセスということはexecve(2) ...)、その後(含むプロセスの内部状態の再初期化ユーザ・モード・レジスタ)。動的リンカーとして-such ld-linux.so(8) Linux-上の実行時に関与している可能性があります。Operating System:Three Easy Piecesなどの良い本を読んでください。OSDEVのウィキにも有用な情報を与えています。

PS。あなたの質問は非常に広範であるため、それに関するいくつかの本を読む必要があります。いくつかの(非常に不完全な)参照を提供しました。それらをもっと見つける必要があります。


1
オブジェクトファイル形式については、初心者にはNASMで作成されたRDOFF形式を参照することをお勧めします。これは、現実的に可能な限り単純になるように意図的に設計されており、さまざまな状況で機能します。NASMソースには、フォーマット用のリンカーとローダーが含まれています。(完全な開示-私はこれらすべてを設計し、書いた)
ジュール
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.