アセンブリからマシンコード(コード生成)に移行するにはどうすればよいですか?

コードをアセンブルしてからマシンコードに移行するステップを視覚化する簡単な方法はありますか?

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

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

回答

命令セットのドキュメントを見ると、各命令のPICマイクロプロセッサ

追加命令の例

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

最初の数ビットは「オペコード」と呼ばれ、命令ごとに一意です。CPUは基本的に、オペコードを調べて命令を確認し、「k」を追加する数値としてデコードすることを認識しています。

面倒ですが、エンコードとデコードはそれほど難しくありません。学部生のクラスで、試験で手作業で行う必要がありました。

実際に完全な実行可能ファイルを作成するには、メモリの割り当て、ブランチオフセットの計算、およびそれをオペレーティングシステムに応じて、 ELF のような形式。

回答

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

もちろん、オペレーティングシステムで適切にロードおよび実行される実行可能ファイルを作成するには、それ以上の時間がかかります。ほとんどの適切なアセンブラはこれを行います。オペコードをマシン命令(マクロなど)に単純にマッピングする以外にも、いくつかの追加機能があります。

回答

最初の必要なのは、このファイルのようなものです。これは、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は次のとおりです。オペランドの説明:1つのmodr/m(レジスタまたはメモリ)オペランド(つまり、modr/mバイトをに追加する必要があります命令の終わり(後で説明します)と即時命令(命令の説明で使用されます)。
  • 次はhleです。これは、「ロック」プレフィックスの処理方法を識別します。 「ロック」を使用していないため、無視します。
  • 次はo32です。これは、「16-のコードをアセンブルしている場合」を示しています。ビット出力フォーマットの場合、命令にはオペランドサイズのオーバーライドプレフィックスが必要です。16ビット出力を生成する場合は、ここでプレフィックス(0x66)を生成しますが、そうではないと仮定して続行します。
  • 次は83です。これは16進数のリテラルバイトです。出力します。
  • 次は/0。これにより、modr / mバイトに必要な追加ビットが指定され、生成されます。modr/mバイトは、レジスタまたは間接メモリ参照をエンコードするために使用されます。このようなオペランドは1つ、レジスタです。レジスタには番号があり、別のデータファイルで指定されています:

     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フィールドは。

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

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

命令ごとに繰り返します。ラベルが参照されたときに何を挿入するかがわかるように、ラベルを追跡します。オブジェクトファイル出力モジュールに渡されるマクロとディレクティブの機能を追加します。そして、これが基本的にアセンブラの仕組みです。

コメント

  • ありがとうございます。すばらしい説明ですが、'ではなく" 0x83 0xC0 0x2A "にする必要がありますid = “126a74f238″>

0x83 0xB0 0x2A " 0b11000000 = 0xC0

  • @ 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 …ええ、あなたは'まったく正しいです。 🙂
  • 回答

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

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

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

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

    ほとんどのアセンブラは 2つのパス。2番目のパスは再配置を発行するか、最初のパスの出力の一部を修正します。彼らは現在、通常の解析手法を使用しています(したがって、おそらくドラゴンブックを読んでください)。

    OS カーネルによる実行可能ファイルの起動方法(例:Linuxでのexecveシステムコールの動作)は別の(そして複雑な)質問です。通常、仮想アドレス空間を設定します(プロセス execve(2) …)次に、プロセスの内部状態を再初期化します(ユーザーモードレジスタを含む)。 ダイナミックリンカー -Linux上の ld-linux.so(8)など実行時に関与します。 オペレーティングシステム:3つの簡単なピース などの優れた本を読んでください。 OSDEV wikiも有用な情報を提供しています。

    PS。あなたの質問は非常に広いので、それについていくつかの本を読む必要があります。私はいくつかの(非常に不完全な)参照を与えました。

    コメント

    • オブジェクトファイル形式に関して、初心者向けI ' dNASMによって作成されたRDOFF形式を確認することをお勧めします。これは、現実的に可能な限り単純でありながら、さまざまな状況で機能するように意図的に設計されています。 NASMソースには、フォーマット用のリンカーとローダーが含まれています。 (完全な開示-私はこれらすべてを設計および作成しました)

    コメントを残す

    メールアドレスが公開されることはありません。 * が付いている欄は必須項目です