ループとメモリ操作

ループ
> 汎用命令 (3)

ループを行いたい場合は、一つのレジスタ (またはメモリ位置の値) をカウンタとして使い、以下のような処理を行います。
カウンタとして使われるのは、主に rCX レジスタです。

  1. ループ開始時、カウンタに、ループを行う回数の値を入れておく。
  2. ループ内の処理を行う。
  3. 処理の終端で、カウンタの値をデクリメントする。
  4. その結果が 0 でない場合 (ZF = 0)、処理の先頭にジャンプさせる。
  5. カウンタの値が 0 になった場合、ジャンプせず、後続の命令を実行する。

    mov ecx, 10 ; カウンタ (10回)
loop1:
    ...
    dec ecx   ; ecx--
    jnz loop1 ; ecx が 0 でない場合ジャンプ
    ; ループ終了時はここに続く

rCX レジスタをカウンタとして使う場合は、ループに関連した命令がいくつかあります。
LOOPcc 命令
LOOP rel8off   | E2 cb
LOOPE rel8off  | E1 cb | ZF = 1
LOOPZ rel8off  | E1 cb | ZF = 1
LOOPNE rel8off | E0 cb | ZF = 0
LOOPNZ rel8off | E0 cb | ZF = 0

LOOPcc 命令は、ループにおけるデクリメントと条件ジャンプを、一つの命令で行うことができます。

カウンタには、rCX レジスタを使います。
カウンタレジスタのサイズは、アドレスサイズ属性によって決まります。

64bit モードの場合、アドレスサイズはデフォルトで 64bit です。67h プレフィックスがある場合、32bit になります。
よって、64bit モードでは、ECX か RCX しか使用できません。

この命令では、8bit のオフセット値しか指定できないので、注意してください。
ジャンプ先が -128〜127 のオフセット値を超える場合は、通常の命令を使う必要があります。
(オフセットが 8bit の範囲を超える場合、NASM の方でエラーが出ます)

実際のところは、レジスタが固定されていることや、オフセット値の制限があるため、あまり使い勝手はよくありません。
メモリ操作
MOVSx 命令などの、メモリ操作系は、RSI と RSI レジスタを、ソースと宛先のアドレス位置として使います。

命令が実行されるごとに、RSI と RDI の値は、メモリから読み書きしたサイズ分だけ、加算または減算されます。

値を加算するか減算するかは、rFLAGS の DF フラグによって決まります。
DF = 0 なら加算、DF = 1 なら減算します。

DF フラグは、CLD 命令で 0 に、STD 命令で 1 に変更できます。
現在の DF フラグが 0 なのか 1 なのかがわからないような状況では、常にフラグを変更しておく必要があります。
繰り返しプレフィックス
MOVSx などの命令では、以下のプレフィックスを付けることで、rCX レジスタで指定された回数分、命令を繰り返すことができます。

REP (F2h)rCX が 0 になるまで繰り返す。
メモリ内の値の比較を行わない命令で使います。
REPE or REPZ (F2h)rCX が 0 でなく、ZF = 1 の場合に繰り返す。
(rCX が 0 か、ZF が 0 になるまで繰り返す)

メモリ内の値の比較を行う命令で使います。
(プレフィックスのバイト値は、REP と同じです)
REPNE or REPNZ (F3h)rCX が 0 でなく、ZF = 0 の場合に繰り返す。
メモリ内の値の比較を行う命令で使います。

NASM でも、命令の前に上記のプレフィックスを付けることができます。
サンプルコード
srcdata の 32bit x 5 の値を、dstdata に 4byte 単位でコピーします。

default rel
global testfunc

section .data

srcdata: dd 1,2,3,4,5
dstdata: dd 0,0,0,0,0

section .text

testfunc:
    cld    ; DF = 0
    lea rsi, [srcdata] ; ソース
    lea rdi, [dstdata] ; 宛先
    mov ecx, 5  ; 回数 (64bit ゼロ拡張)
    rep movsd   ; コピー
    ret

rCX のサイズ
rCX レジスタがどのサイズとして使われるかは、命令のアドレスサイズによって決まります。

64bit モードの場合は、デフォルトは 64bit です (RCX)。
アドレスサイズ・プレフィックス (67h) がある場合、32bit (ECX) になります。

ただし、ECX に値を代入するということは、常に 64bit にゼロ拡張されるということなので、わざわざ 32bit サイズを指定する必要はないでしょう。
基本的に RCX が使われるものだと思ってください。
メモリの値の比較
SCASx 命令で、RDI の位置のメモリと、rAX の値を比較することができます。
※RSI ではなく、RDI レジスタが使用されるので、注意してください。

default rel
global testfunc

section .text

string: db "abcdef",0

testfunc:
    cld
    lea rdi, [string]
    mov rbx, rdi  ; 先頭位置のアドレスを記憶
    xor al, al    ; al = 0

    ; AL = 0 と等しくなるまで読み込み
loop1:
    scasb
    jne loop1

    sub rdi, rbx ; RDI(0 の次の位置) - RBX(string) = 7
    ret

string のバイトデータの中から、0 の値が見つかるまでループし、RDI に、先頭からのバイト数を計算します (ただし、見つかった 0 の次の位置)。

SCASB 命令で、RDI のメモリ位置からバイトを読み込み、AL の値 0 と比較して、値が等しいか (ZF = 1) を判断します。
等しくない場合は、loop1 にジャンプします。

繰り返しのプレフィックス REPx は、rCX レジスタの値を回数としてチェックするため、回数が可変数の状況では使用できません。

0 の値が見つかった場合 (ループ終了後)、RDI には、見つかった 0 の次の位置のアドレスが入っています。
そこから string の先頭位置のアドレスを引くと、結果は 7 になります。
(0 の値を比較した後、RDI がインクリメントされるため)

これにより、string のデータは、0 も含めて、7 byte の文字列であることが判断できます。