C プログラムをアセンブリに変換する

Abdul Mateen 2023年10月12日
  1. アセンブリ言語
  2. C言語
  3. C プログラムをアセンブリ言語に変換する
C プログラムをアセンブリに変換する

このチュートリアルでは、C 言語プログラムをアセンブリ言語コードに変換する方法について説明します。

アセンブリ言語と C 言語の基礎について簡単に説明します。 後で、C プログラムのアセンブリ コードへの変換と、アセンブリ コードの逆アセンブルについて説明します。

アセンブリ言語

アセンブリは、低レベルのインタープリター言語です。 一般に、アセンブリ言語で記述されたステートメントは、単一の機械レベルの命令に変換されます。

ただし、ニーモニックを使用しているため、機械語よりもはるかに読みやすいです。 ニーモニックは、英語のような命令または操作コードです。

たとえば、ニーモニック ADD は 2つの数値を加算するために使用されます。 同様に、MOV はデータの移動を実行するために使用されます。

同様に、CMP は 2つの式を比較し、JMP は実行コントロールを特定のラベルまたはロケーション マーカーにジャンプします。

アセンブリ言語はマシン (ハードウェア) に非常に近いものです。 したがって、アセンブリ言語で記述された命令は非常に高速です。 ただし、プログラマーは高級言語の開発者よりもはるかに多くのハードウェア知識を持っている必要があります。

アセンブリ言語は通常、デバイス ドライバ、ウイルス/ウイルス対策プログラム、組み込みシステム ソフトウェア、TSR (終了および常駐プログラム) などの効率的なシステム プログラムを作成するために使用されます。

アセンブラは、アセンブリ言語プログラムをマシン上で実行可能な機械語プログラムにアセンブルする必要があります。

C言語

C は、機械に依存しない高水準のプログラミング言語です。 通常、C プログラムはハードウェアの知識を必要としません (ほんの少しの知識しか必要ありません)。

C には高レベルのステートメントがあり、C 言語の各ステートメントを 1つまたは複数のアセンブリ言語ステートメントに変換するコンパイラ プログラムが必要です。 たとえば、C 言語の単純な命令 c = a + b は、次のアセンブリ言語ステートメントに変換されます。

mov edx, DWORD PTR - 12 [rbp] mov eax, DWORD PTR - 8 [rbp] add eax,
    edx mov DWORD PTR - 4 [rbp], eax

ここで、最初と2番目のステートメントで、メモリからの変数の値がレジスタに移動されます。 add 命令は、2つのレジスタ値を加算しています。

4 番目のステートメントでは、レジスタの値がメモリ内の変数に移動されます。

その上、コンパイラは多くの作業を行わなければなりませんが、プログラマーの生活は C 言語で作業するだけです。 C 言語には、高レベルのビジネス アプリケーションから低レベルのユーティリティ プログラムまで、幅広いアプリケーションがあります。

C プログラムをアセンブリ言語に変換する

通常、高度な統合環境を使用して C 言語プログラムを作成、編集、コンパイル、実行、変更、およびデバッグするか、gcc コマンドを使用して C 言語プログラムを実行可能プログラムに変換します。

これらのツールを使用すると、C などの高級言語で記述されたソース コードをマシンで実行可能なコードに変換する際の手順を、ユーザーが認識しなくなります。 通常、次の手順がその間に実行されます。

  1. 前処理 - プリプロセッサ プログラムは 3つのタスクを実行します。 1つ目はヘッダー ファイルのインクルード、2つ目はマクロの置き換え、3つ目はソース プログラムからのコメントの削除です。
  2. コンパイラ - 2 番目のステップでは、コンパイラが高級言語プログラムをアセンブリ言語プログラムに変換します。
  3. アセンブラー - 3 番目のステップでは、アセンブラー プログラムはアセンブリ言語プログラム (コンパイラによって変換されたもの) を受け取り、それをオブジェクト コードと呼ばれる機械で実行可能な形式にアセンブルします。
  4. リンカ - 4 番目のステップでは、リンカ プログラムが、このプログラムを独立して実行するオブジェクト コードを含むコンパイル済みライブラリ ファイルをアタッチします。

C コードを同等のアセンブリに変換するコマンド

通常、コマンド ライン ユーザーは gcc program_name.c と入力します。これにより、実行可能ファイルが生成されます (エラーがない場合)。 ターゲット ファイル名が指定されていない場合は、UNIX オペレーティング システム ファミリの a.out または Windows オペレーティング システムの program_name.exe で使用できます。

それでも、gcc コマンドには、特定のタスクを実行するためのパラメーターの膨大なリストがあります。 このチュートリアルでは、-s フラグと -C フラグについてのみ説明します。

-S フラグは、C ソース コードからアセンブリ言語プログラムを生成します。 ソースファイルとして test.c がある次の例を使用して、このフラグを理解しましょう。

// test.c
int main() {
  int a = 2, b = 3, c;
  c = a + b;
  return 0;
}

次のコマンドは、拡張子 .S を持つターゲット アセンブリ言語コードを生成します。

$ gcc -S test.c
$ ls
test.c test.s

コマンドは機械語コードを作成していません。 アセンブリ言語コードのみが生成されます。 Bash で cat コマンドを使用して、この生成されたアセンブリ コードの内容を表示してみましょう。

$ cat test.s
    .file   "Test.c"
    .text
    .globl  main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    endbr64
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    $2, -12(%rbp)
    movl    $3, -8(%rbp)
    movl    -12(%rbp), %edx
    movl    -8(%rbp), %eax
    addl    %edx, %eax
    movl    %eax, -4(%rbp)
    ...

生成されたアセンブリ コードは、Intel x86 アーキテクチャ用のアセンブリ コードを記述した経験のある多くのプログラマには馴染みがないかもしれません。

Intel x86 アーキテクチャのターゲット アセンブリ コードが必要な場合は、次のコマンドでこれを行います。

$ gcc -S -masm=intel  Test.c

ここでも、出力は Test.s ファイルに生成されます。このファイルは、Bash ターミナルで cat コマンドを使用して表示できます。 Windows では、メモ帳などのエディターまたはより優れたエディターで開くことができます。

とにかく、上記のコマンドによって生成されたアセンブリ コードの内容を見てみましょう。

 cat Test.s
    .file   "Test.c"
    .intel_syntax noprefix
    .text
    .globl  main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    endbr64
    push    rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    mov rbp, rsp
    .cfi_def_cfa_register 6
    mov DWORD PTR -12[rbp], 2
    mov DWORD PTR -8[rbp], 3
    mov edx, DWORD PTR -12[rbp]
    mov eax, DWORD PTR -8[rbp]
    add eax, edx
    mov DWORD PTR -4[rbp], eax
    ...

出力はわずかに異なります。 movadd コマンドは非常に明確です。

オブジェクト コードの逆アセンブル

C 言語プログラムをアセンブリ言語に変換するだけでなく、バイナリ コード (マシン コード) を逆アセンブルして、同等のアセンブリ言語コードを表示することもできます。 これを行うには、Linux の objdump ユーティリティを使用できます。

例:

gcc -c Test.c コマンドを実行して Test.c ファイルを Bash ターミナルでコンパイルするとします。 Test.o という名前のオブジェクト ファイル (機械語コード) を作成します。

ここで、このオブジェクト コードを同等のアセンブリ コードに再変換/逆アセンブルしたい場合は、次の Bash コマンドを使用して実行できます。

$ objdump -d Test.o

Test.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
   0:   f3 0f 1e fa             endbr64
   4:   55                      push   %rbp
   5   48 89 e5                 mov    %rsp,%rbp
   8:   c7 45 f4 02 00 00 00    movl   $0x2,-0xc(%rbp)
   f:   c7 45 f8 03 00 00 00    movl   $0x3,-0x8(%rbp)
  16:   8b 55 f4                mov    -0xc(%rbp),%edx
  19:   8b 45 f8                mov    -0x8(%rbp),%eax
  1c:   01 d0                   add    %edx,%eax
  1e:   89 45 fc                mov    %eax,-0x4(%rbp)
  21:   b8 00 00 00 00          mov    $0x0,%eax
  26:   5d                      pop    %rbp

この出力では、左側のコードは 16 進数のバイナリ コードです。 右側には、読み取り可能な形式のアセンブリ言語コードが表示されます。