x86汇编语言中CLD和STD的作用是什么?DF有什么作用?

36

我知道CLD会清除方向标志(direction flag),而STD会设置方向标志。但是,设置和清除方向标志有什么意义呢?


5
哎呀,咬人的虫子太多了,我的 Turbo Pascal 代码被嵌入了内联汇编来重置它。 - Hans Passant
5个回答

49
方向标志用于影响字符串指令偏移指针寄存器的方向。这些指令可以使用REP前缀来重复操作。(虽然lodsrep不太有用)。
字符串指令包括:MOVS(将内存复制到内存)、STOS(存储AL/AX/EAX/RAX)、SCAS(扫描字符串)、CMPS(比较字符串)和LODS(加载字符串)。还有ins/outs用于在内存和IO端口之间进行复制。每个指令都有字节、字、双字和四字操作数大小可用。
简而言之,当方向标志为0时,指令会在每次迭代后将指针递增到数据后面(直到ECX为零或其他条件,具体取决于REP前缀的类型),而如果标志为1,则指针会递减。
例如,movsd将一个双字从[ds:esi]复制到[es:edi](在64位模式下是rdi),并执行以下操作:(请参见链接的ISA参考手册条目中的“操作”部分,该手册从英特尔的PDF文件中提取)
dword [es:edi] = dword [ds:esi]      // 4-byte copy memory to memory
if (DF == 0)
    esi += 4;
    edi += 4;
else  // DF == 1
    esi -= 4;
    edi -= 4;
fi

使用REP前缀,它会执行ECX次操作,现代的x86 CPU具有优化的“快速字符串”微码,可以使用16字节或32字节的内部操作进行复制(或stos存储)。请参见关于内存带宽和ERMSB特性的此问答。(注意,仅rep stosrep movs被以这种方式优化,而不是repne/repe scascmps。)


2
这是一个具有断言的最小化可运行示例 - Ciro Santilli OurBigBook.com

12

CLD指令清除方向标志位,数据正向传送。 STD指令设置方向标志位,数据反向传送。


4
如果使用 Windows,则根据 STDCALL 调用约定 -
在 STDCALL 中,进入时方向标志被清除,必须返回清除状态。
因此,如果设置了 DF,则在 API 调用之前必须将其清除。

操作系统相关。 - amanuel2
这适用于大多数32位/64位调用约定,包括i386 System V和x86-64 System V。它使您(或编译器)能够有效地内联 rep movsd / rep stosd 而无需 CLD 指令。(在现代x86上,它们通常只能向上快速移动,DF=0) - Peter Cordes

4

CLD: 清除方向标志,使字符串指针在每个字符串操作后自动增加

STD: std 用于将方向标志设置为1,以便在执行任何一个字符串指令时,SI和/或DI将自动减少以指向下一个字符串元素。如果设置了方向标志,则对于字节字符串,SI/DI将减少1,对于字字符串,SI/DI将减少2。

这个答案可能对您有所帮助。


3

CLD:清除EFLAGS寄存器中的DF标志。当DF标志设置为0时,字符串操作会递增索引寄存器(ESI和/或EDI)。

这里有一个简单的例子:

section .text
global main
main:
    mov ecx, len
    mov esi, s1
    mov edi, s2

    cld       ; redundant because DF is already guaranteed to be 0 on function entry
              ; but included for illustration purposes

loop_here:
    lodsb                ; AL=[esi],  ESI+=1 (because DF=0, otherwise ESI-=1)
    add al, 02
    stosb                ; [edi]=AL,  EDI+=1 (because DF=0, otherwise EDI-=1)
    loop loop_here       ; like dec ecx / jnz but without setting flags
    ; ECX=0, EDI and ESI pointing to the end of their buffers

    mov edx, len-1       ;message length, not including the terminating 0 byte
    mov ecx,s2           ;message to write
    mov ebx,1            ;file descriptor (stdout)
    mov eax,4            ;system call number (sys_write)
    int 0x80             ;call kernel

    mov  eax,1           ;system call number (sys_exit)
    xor  ebx,ebx
    int  0x80            ;call kernel: sys_exit(0)

section .data
s1: db 'password', 0        ; source buffer
len equ $-s1

section .bss
s2: resb len                ; destination buffer

(使用nasm -felf32 caesar.asm && gcc -no-pie -m32 caesar.o -o caesar进行汇编和链接。如果您喜欢,也可以将其作为_start而不是main链接到静态可执行文件中。)

(此示例尝试实现凯撒密码。)


1
i386 System V 调用约定已经保证函数进入时 DF=0,这就是为什么您的程序在运行 cld 之前使用 lodsb/stosb 仍然能够可靠工作的原因。rep movsb 在 ECX=0(来自 loop)的情况下运行,因此它会将零字节复制到 s1/s2 的末尾,并且此时设置 DF 的值并不重要。 - Peter Cordes
顺便说一句,loop很慢,除非你优化代码大小而不是速度,否则不要使用它。此外,您可能希望将字母表的最后2个字母包装到前2个字母中。因此,如果您要实现包装检查,则有意义加载并在寄存器中操作一个字节。否则,您可以使用add byte [esi], 2 / inc esi原地修改字符串。或者,如果不会进位,可以一次处理4个字节,例如add dword [esi], 0x02020202 - Peter Cordes
谢谢。我会尝试修复您在我的代码中提到的问题 :) - N.S

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接