x86 および x86-64 向け NASM

最終更新: 2025-01-13 21:14:39 +0800 +0800
tags: Nasm | Assembly | X86
原文は英語で書かれています。あなたはAI翻訳版を読んでいます。

NASM は Intel 構文の x86 アセンブリを、フラットバイナリまたはオブジェクトファイルに変換します。 フラットバイナリには出力されたバイト列が含まれますが、標準的な再配置情報やシンボルの メタデータは含まれません。ELF、Mach-O、COFF などのオブジェクトファイルは、セクション、 シンボル、再配置情報を保持するため、リンカーはほかのオブジェクトやライブラリと結合できます。

セクション、ラベル、シンボル #

オブジェクトファイル用のソースでは、通常、実行可能コード、初期化済みデータ、 ゼロ初期化されるストレージを分けます。

nasm
global add_one
extern external_value

section .text
add_one:
    mov eax, edi
    add eax, 1
    ret

section .rodata
message: db "hello", 10
message_size equ $ - message

section .bss
scratch: resq 8

ラベルは現在のアセンブリ位置を定義します。GLOBAL はシンボルをリンカーへ公開し、 EXTERN は別のオブジェクトで定義される必要があるシンボルを宣言します。. で始まる ローカルラベルのスコープは、直前の非ローカルラベルです。セクション名と属性は出力形式に 依存します。.text、.rodata、.data、.bss は ELF の慣例であり、NASM に普遍的な キーワードではありません。

-f bin で選択するフラットバイナリには、仮想アドレスを割り当てるリンカーがありません。 ORG はアドレス計算に使用する起点を変更しますが、パディングを挿入することも、 そのアドレスへバイト列をロードすることもありません。ブートセクター、ファームウェア、 組み込みイメージでは、独自にレイアウトと充填要件を定める必要があります。

実効アドレスと位置独立性 #

NASM ではデスティネーションオペランドを先に記述します。角括弧はメモリの実効アドレスを 表します。

nasm
mov rax, value          ; address/immediate determined by relocation rules
mov rbx, [value]        ; load from memory
mov ecx, [base + index*4 + 8]
lea rdx, [base + index*4 + 8]  ; calculate address; do not load

x86 のアドレッシングでは、モード固有のエンコード規則に従って、ベース、1、2、4、8 の いずれかを乗じたインデックス、ディスプレースメントを使用できます。メモリオペランドの サイズをレジスターから判定できない場合、byte、dword、qword などの修飾子を 明示する必要があります。

BITS 16、BITS 32、BITS 64 は、対象となる命令エンコードモードを選択します。 実行時にプロセッサーのモードを切り替えるものではありません。オブジェクト形式では適切な デフォルトが設定されます。-f bin のデフォルトは 16 ビットモードであるため、フラットな 32 ビットまたは 64 ビットコードではモードを明記する必要があります。

64 ビットモードでは、DEFAULT REL により、レジスターを使用しないメモリ参照が、 ABS で上書きされない限り RIP 相対になります。FS と GS のオーバーライドには、 NASM の文書に記載された特別なデフォルトがあります。RIP 相対参照は ±2 GiB 以内の コードまたは静的データへ到達でき、位置独立コードに有用です。位置独立性は、オブジェクト 形式、再配置タイプ、コードモデル、シンボルの可視性、リンカーにも依存します。 DEFAULT REL だけでは、任意の外部シンボルや絶対アドレスの使用を位置独立にできません。

データの定義、予約、アラインメント #

DB、DW、DD、DQ、DT、DO、DY、DZ は、順にサイズが大きくなる 初期化済みの値を出力します。一般的な定義を次に示します。

nasm
bytes:  db 0x7f, "NASM", 0
words:  dw 1, 2, 3
value:  dq 42
table:  times 16 dd 0

RESB、RESW、RESD、RESQ は、それらをサポートするセクションで未初期化 ストレージを予約します。EQU はアセンブル時の定数を定義し、$ は現在位置を表します。 BSS のようなセクションは初期化済みの内容ではなくサイズを記録するため、予約によって オブジェクトファイルに必ずしもバイトが追加されるわけではありません。

ALIGN n は指定された境界まで位置を進め、引数と現在のセクションに従って間隙を 埋められます。オブジェクト内のアラインメントはセクションのアラインメントとリンカーの 配置にも依存するため、アラインメントが不足したセクション内でオフセットだけを整列させても 不十分です。セクション全体でより強い保証が必要な場合は、SECTION ... align=n または 形式固有の仕組みを使用してください。

プリプロセッサーマクロとインクルード #

NASM はアセンブル前にトークンベースのプリプロセッサーを実行します。

  • %define と %xdefine は、展開のタイミングが異なる単一行マクロを定義します。
  • %macro と %endmacro は複数行マクロを定義し、%1、%2 などは位置引数を 表します。
  • %if、%ifdef、%elif、%endif はソースを条件に応じて選択します。
  • %include は別のソースファイルを読み込み、-I はインクルード検索パスを追加します。
  • %assign は数値のプリプロセッサー変数を保持します。
nasm
%ifndef WORD_SIZE
    %define WORD_SIZE 8
%endif

%macro save_pair 2
    push %1
    push %2
%endmacro

マクロが生成するのはソースであり、呼び出し可能なルーチンではありません。マクロはコードを 複製したり、名前を捕捉したり、予想外の診断を発生させたりすることがあるため、その展開を 調べる際にはリスティング(-l file.lst)と逆アセンブルが役立ちます。共有マクロファイルが 複数回インクルードされた場合、インクルードガードは多重定義を防ぎます。

ABI に合わせたアセンブルとリンク #

出力形式と ABI は別個の選択です。64 ビット Linux System V を対象とする場合は、 次のように実行します。

sh
nasm -f elf64 -g -F dwarf -o add_one.o add_one.asm
cc -o demo main.c add_one.o

32 ビット Linux を対象とする場合は、-f elf32 と、cc -m32 など必要な 32 ビット ランタイム向けに構成されたリンカーまたはコンパイラーを使用します。Windows x64 では win64 と Microsoft x64 呼び出し規約を使用し、macOS では macho64 と Mach-O の シンボル規則を使用します。これらの形式間でオブジェクトファイルに互換性はありません。

手書きの関数は、対象 ABI の引数レジスター、スタックアラインメント、戻り値、保存対象 レジスター、アンワインドメタデータ、シンボル命名規則を実装する必要があります。Linux の syscall シーケンスは、ユーザー空間の関数 ABI ではなくカーネルのシステムコール ABI に 従います。C または C++ ランタイムの起動処理、デフォルトライブラリ、位置独立実行可能 ファイルのポリシー、プラットフォーム固有のリンカーオプションが必要な場合は、 コンパイラードライバーを使用してリンクしてください。

NASM の現行マニュアルでは、ディレクティブと形式固有の 動作を定義しています。アセンブラーの範囲外にある規約は、オペレーティングシステムの ABI とプロセッサーのマニュアルで定義されています。

コメント

GitHub で閲覧・コメントする(投稿にはログインが必要です)