0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Netwide Assembler(NASM)ならアラインメント調整にマルチバイトNOP命令を出力できる

Last updated at Posted at 2025-08-17

はじめに

昨今の x86 プロセッサ用 C コンパイラはフェッチ効率を上げるためジャンプ先のアドレスが 16 バイトの整数倍になるようにアラインメント調整を行うが,この際に 1~15 バイト長の NOP 命令(通称,マルチバイト NOP 命令)を挿入してアドレスを調整している。

ところが,アセンブラでも同様にジャンプ先アドレスを 16 バイトの整数倍に並べようとして組み込みの ALIGN ディレクティブを使用すると jmp 命令などを生成してしまう。現代のプロセッサでは迂闊に jmp 命令を用いるとパイプラインが乱れてパフォーマンスが著しく低下してしまうのに。

そもそもパフォーマンス向上のためにアセンブラを使っているにも関わらず,アラインメント調整用にマルチバイト NOP 命令を使用できないのはおかしい。もちろん命令長とアドレスを調べて,マルチバイト NOP 命令を直接コード内に埋めることはできるが,あくまで筆者はアラインメント調整およびマルチバイト NOP 命令の生成を自動化したいのだ。

なぜ Netwide Assembler(NASM)か?

  • 筆者の PC には既に Netwide Assembler(NASM)がインストールされていたから。mozjpeg をビルドするときに必要だったのだ1

  • Microsoft Macro Assember(MASM)は1パスだが,Netwide Assembler(NASM)は2パスなので複雑な構文を扱うことができるかもしれない2

  • Microsoft Macro Assembler(MASM)とオブジェクトコード互換のため3

おしながき(仕様案)

  • アラインメントするサイズは 16 バイト固定とする。これはマルチバイト NOP 命令が最大 15 バイト長だからだ。
  • 16 バイト整列用コードはマクロ align16 として定義し,ヘッダファイル align16.inc として分離する。使用する際にはヘッダファイルを %include して呼び出す。

実装コード

この結果を得られるまで試行錯誤を繰り返したが,いきなり結果を見せることにしよう。アラインメント調整用マクロのヘッダファイルを示す。

align16.inc(アラインメント調整用マクロ)
%macro  align16 0
        %%skip  equ     ($ - $$) % 16
        %if %%skip = 15
                db      0x90
        %elif %%skip = 14
                db      0x66, 0x90
        %elif %%skip = 13
                db      0x0F, 0x1F, 0x00
        %elif %%skip = 12
                db      0x0F, 0x1F, 0x40, 0x00
        %elif %%skip = 11
                db      0x0F, 0x1F, 0x44, 0x00, 0x00
        %elif %%skip = 10
                db      0x66, 0x0F, 0x1F, 0x44, 0x00, 0x00
        %elif %%skip = 9
                db      0x0F, 0x1F, 0x80, 0x00, 0x00, 0x00, 0x00
        %elif %%skip = 8
                db      0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00
        %elif %%skip = 7
                db      0x66
                db      0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00
        %elif %%skip = 6
                db      0x66, 0x2E
                db      0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00
        %elif %%skip = 5
                db      0x66, 0x66, 0x2E
                db      0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00
        %elif %%skip = 4
                db      0x66, 0x66, 0x66, 0x2E
                db      0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00
        %elif %%skip = 3
                db      0x66, 0x66, 0x66, 0x66, 0x2E
                db      0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00
        %elif %%skip = 2
                db      0x66, 0x66, 0x66, 0x66, 0x66, 0x2E
                db      0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00
        %elif %%skip = 1
                db      0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x2E
                db      0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00
        %endif
%endmacro

上記のマクロを用いたテスト用コードを示す。なお,Netwide Assembler (NASM) の times 10 db 0x90 は Microsoft Macro Assember(MASM)の db 10 dup(90h) と同じである。

test.asm(テスト用コード)
%include "align16.inc"
        section .text   align=16
        global  _main
_main:
        db      0x90
        align16
        times   2       db      0x90
        align16
        times   3       db      0x90
        align16
        times   4       db      0x90
        align16
        times   5       db      0x90
        align16
        times   6       db      0x90
        align16
        times   7       db      0x90
        align16
        times   8       db      0x90
        align16
        times   9       db      0x90
        align16
        times   10      db      0x90
        align16
        times   11      db      0x90
        align16
        times   12      db      0x90
        align16
        times   13      db      0x90
        align16
        times   14      db      0x90
        align16
        times   15      db      0x90
        align16
        ret

Makefile を示す。メイク nmake.exe およびリンカー link.exe は Visual Studio のものを用いた。

Makefile
target: TEST.EXE

TEST.EXE: TEST.OBJ
	link $** /entry:main

.ASM.OBJ:
	nasm $< -f win32

clean:
	if exist *.OBJ del *.OBJ
	if exist *.EXE del *.EXE

テスト用コードの逆アセンブル結果

Netwide Assembler (NASM) にも逆アセンブラ ndisasm.exe は付属しているが,今回は Visual Studio の dumpbin.exe を使用した。

マルチバイト NOP 命令の箇所を黄色 #FFFF00 で示すが,無事 1~15 バイト長のマルチバイト NOP 命令を出力できている。

解説

Netwide Assembler (NASM) も Microsoft Macro Assember(MASM)と同じく記号 $ を用いて現在のアドレスを得ることができるが,このアドレスはリンク時に決定されるのでアセンブル時には分からない。よって下記のコードは Netwide Assembler (NASM) でもエラーになってしまう。

現在のアドレスはリンク時に決定されるのでアセンブル時には決まらない
 %%skip  equ     $ % 16

ところが Netwide Assembler (NASM) ではセクションの先頭アドレスを記号 $$ を用いて得ることができ,現在のアドレスとの差分 $ - $$ であればアセンブル時に確定するためエラーにならない。

セクション先頭からの差分であればアセンブル時に確定する
 %%skip  equ     ($ - $$) % 16

このマクロを用いる場合は下記のようにセクション自体を 16 バイト境界に整列させておく必要がある。

セクション自体を整列させておくこと
section .text   align=16

基礎的な実験

Netwide Assembler (NASM) において,現在のアドレス $ およびセクション先頭からの相対アドレス $ - $$ がどのように取り扱われているのか確認してみた。具体的には下記の3種類のアドレスをコードセクション内に組み込んだ。

  • @abs 現在のアドレス(絶対アドレス)#00FFFF
  • @rel セクション先頭からの相対アドレス #00FF00
  • @diff プロシジャー先頭からのアドレス差分 #FFFF00

アセンブルしたオブジェクトファイルをダンプしてみると,このうち相対アドレス @rel とアドレス差分 @diff の値は確定しており,絶対アドレス @abs のみ 0x00000001 という暫定値が割り当てられた上で RELOCATION リストに登録されている。なお,リストの DIR32 の意味は 32bit のVA(仮想アドレス)という意味である4

次にリンクして出来た実行イメージファイルをダンプすると,絶対アドレス @abs の値が書き換えられていることが分かる。すなわち絶対アドレスが確定するのはリンク時である。

dumpbin コマンドでオブジェクトファイルのダンプが取れることを今回初めて知った。

  1. mozilla/mozjpeg/BUILDING.md - github

  2. 3.8 Critical Expressions - NASM

  3. WindowsでNASMを使ってアセンブラを動かしてみる - Qiita

  4. PE形式/ファイルのその他の内容/COFF再配置/型インジケーター/intel386プロセッサ - microsoft

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?