方向 EFLAG 的值如何影响 SCAS 和 MOVS 指令?

How are the SCAS and MOVS instructions affected by the value of the direction EFLAG?

我想知道设置或清除方向 EFLAG 如何改变 SCAS 和 MOV 指令递减或递增寄存器的方式。我阅读了一些网页并做出了以下假设,我将在下面列出。

我正在使用 MASM 32 SDK - 不知道是什么版本,我通过 Visual MASM 的下载和安装向导安装 - 使用 Visual MASM 到 wright 和 MASM32 Editor 到 link 并将它们构建到对象和可执行文件中。我使用 Windows 7 Pro 64 位 OS.

华夏科学院

  1. SCAS指令"compares a byte in AL or a word in AX with a byte or word pointed to by DI in ES."因此,要使用SCAS,必须将目标字符串地址移至EDI,并将要查找的字符串移至累加器寄存器(EAX及其变体)。

  2. 设置方向标志然后使用 SCAS 将在使用 32 位系统时从 运行 停止 SCAS。在 32 位系统上,无法强制 SCAS 为 "scan a string from the end to the start."

  3. 任何 REP 指令总是使用 ECX 寄存器作为计数器,并且无论方向标志的值如何,总是递减 ECX。这意味着不可能 "scan a string from the end to the beginning" 使用 REP SCAS。

来源:
SCAS/SCASB/SCASW, Birla Institute of Technology and Science
Scan String, from c9xm.me
SCAS/SCASB/SCASW/SCASD — Scan String, from felixcloutier.com
MASM : Using 'String' Instructions, from www.dreamincode.net/forums

下面是我将在问题中引用的程序的部分代码:

;Generic settings from MASM32 editor 
.386
.model flat, stdcall
option casemap: none

.data?
Input db 254 dup(?)
InputCopy db 254 dup(?)
InputLength dd ?, 0
InputEnd dd ?, 0

.data

.code

start:
push 254
push offset Input
call StdIn
mov InputLength, eax

;---Move Last Word---
lea esi, offset Input
sub esi, 4
lea edi, offset InputEnd
movw

;---Search section---
lea esi, Input
lea edi, InputCopy
movsb

mov ecx, InputLength
mov eax, 0
mov eax, "omit"

lea edi, offset InputEnd
repne scasw
jz close ;jump if a match was found and ZF was set to 1.
  1. "Search" 部分下的代码一次搜索字符串 InputEnd 4 个字节,因此一次搜索 4 个字符。该块扫描 EAX 中的字符,即单词 "omit",始终从 edi 中的内存地址值开始,然后根据 SCAS (B, W, D, Q)[=17= 的后缀递增].

MOVS

  1. 使用 "Move Last Word" 部分,我能够从字符串 Input 中获取最后一个字节。然后我使用 MOVSW 将字符串 Input 的最后 4 个字节移动到 InputEnd,假设方向标志已清除。我必须将输入定义为字节数组 - Input db 32 dup(?) - 块才能工作。

  2. 无论我如何定义 InputEnd(无论是 "dd ?, 0" 还是 "db 12 dup(?)"),mov 和 scas 指令的操作(设置标志、修改寄存器等)都不会改变。 increment/decrement SCAS 和 MOV 的数量取决于命令的 suffix/last 字母,而不是定义的字节或存储在 EDI 和 ESI 中的指针的大小。

  3. MOVS无法从字符串的开头到结尾进行传输。你必须是字符串的长度;加载相应地址到EDI和ESI;将字符串的长度添加到 EDI 和 ESI 中存储的地址;最后,使用 std 设置方向标志。这里的一个危险是目标地址低于源或目标字节。

  4. 不可能使用 MOVS 反转字符串的字母,因为 EDI 和 ESI 要么都递减,要么都递增 MOVS.

来源(除了先前在 SCAS 部分列出的网站):
https://c9x.me/x86/html/file_module_x86_id_203.html
http://faydoc.tripod.com/cpu/movsd.htm

这些假设是否正确? 网站 URL 上的 x86 文本是否表示网站信息有误?

首先,repe/repne scascmps 并不快。此外,"fast strings" / ERMSB 微码 rep movsrep stos 仅在 DF=0 时才快(正常/转发/递增地址)。

DF=1 的

rep movs 很慢。 repne scasw 总是 慢。不过,在您针对代码大小进行优化的极少数情况下,它们可能很有用。


您链接的文档详细说明了 movsscas 是如何受 DF 影响的。 阅读英特尔手册中的操作部分。

请注意,它始终是 post-increment/decrement,因此比较的第一个元素不依赖于 DF,仅更新到 EDI and/or ESI。

您的代码仅依赖于 repne scasw 的 DF。 movsb 增加 (DF=0) 或减少 (DF=1) EDI 并不重要,因为您在下次使用前覆盖了 EDI。


repne scasw 是使用 AX 的 16 位 "word" 大小,就像您链接的英特尔手册的 HTML 摘录中所说(https://www.felixcloutier.com/x86/scas:scasb:scasw:scasd)。这是增量 比较宽度。

如果你想要 EAX 的重叠双字比较,你不能使用 scasw

可以在一个循环中使用scasd,但是您必须递减edi来创建重叠。所以如果你只想检查偶数位置,你应该只使用普通的 cmp [edi], eaxadd edi, 2

(或者最好使用 SSE2 SIMD pcmpeqd 来实现 memmem 以进行 4 字节搜索 "needle"。查看像 glibc 这样的优化实现以获得想法,或者 strstr 实现但是在 "haystack" 中取出 0 终止符的支票。)

repne scasd 实现 strstr 或 memmem,它只搜索单个元素。使用 byte 操作数大小,它实现 memchr.


On 32 bit systems, it is impossible to force SCAS to "scan a string from the end to the start."

rep scas 根本不对(隐式长度)C 风格字符串进行操作;它适用于明确长度的字符串。因此,您可以将 EDI 指向缓冲区的最后一个元素。

strrchr 不同,您不必 找到 字符串的结尾以及最后一个匹配项,您 知道 / 可以计算出字符串的结尾在哪里。也许称他们为 "strings" 是问题所在; x86 rep-string 指令实际上适用于已知大小的缓冲区。这就是为什么他们在 ECX 中进行计数并且不会在终止 0 字节处停止。

使用lea edi, [buf + ecx - 1]设置为stdrep scasb。或者 lea edi, [buf + ecx*2 - 2] 在具有 ECX word 元素的缓冲区上设置向后 rep scasw。 (生成指向最后一个元素的指针=buf + size - 1=buf-1 + size)

Any REP instruction always uses the ECX register as a counter and always decrements ECX regardless of the direction flag's value. This means it is impossible to "scan a string from the end to the beginning" using REP SCAS.

这毫无意义。当然是递减的; ECX=0 是搜索在不匹配时结束的方式。如果想从末尾搜索后计算相对于末尾的位置,你可以做 length - ecx 或类似的东西。或者在 EDI 上做指针减法。

6: not the data type of registers stored in EDI and ESI.

汇编语言没有类型;那是更高层次的概念。对 asm 中的正确字节做正确的事情取决于你。 EDI / ESI 寄存器;存储在其中的指针只是在 asm 中没有类型的整数。你不"store a register in EDI",它一个寄存器。也许你想说 "pointer store in EDI"?寄存器没有类型;寄存器中的位模式(又名整数)可以是带符号的 2 的补码、无符号、指针或您想要的任何其他解释。

但是,是的,一旦寄存器中有了指针,MASM 基于您定义符号的方式所做的任何魔法就完全消失了。

记住 movsd 只是 x86 机器代码中的一个 1 字节指令,只是操作码。它只有 3 个输入:DF,以及 EDI 和 ESI 中的两个 32 位整数,并且它们都是隐式的(由操作码字节隐含)。没有其他上下文可以影响硬件的功能。每条机器指令都有其对机器架构状态的影响;仅此而已。

7: It is impossible to make MOVS transfer from the beginning to the end of a string. ... std

不,std 使传输从头到尾倒退。 DF=0 是正常/向前的方向。调用约定保证/要求在任何函数的进入和退出时 DF=0,因此在使用字符串指令之前不需要 cld;您可以假设 DF=0。 (通常您应该保留 DF=0。)

8: It is impossible to reverse a string's letters using MOVS since EDI and ESI are either both decremented or both incremented by MOVS.

没错。 lods / std / stos / cld 循环与使用 decsub 的普通循环相比是不值得的的指针。您可以对读取部分使用 lods 并手动向后写入。你可以通过加载一个双字并使用 bswap 在寄存器中反转它来快 4 倍,所以你正在复制 4 个反转字节的块。

或就地反转:2 次加载到 tmp regs,然后 2 次存储,然后将指针移向彼此直到它们交叉。 (也适用于 bswapmovbe


您的代码中其他奇怪的低效问题:

    mov eax, 0                ;; completely pointless, EAX is overwritten by next instruction
    mov eax, "omit"

此外,具有 disp32 寻址模式的 lea 是对代码大小的毫无意义的浪费。仅将 LEA 用于 64 位代码中的静态地址,用于 RIP 相对寻址。请改用 mov esi, OFFSET Input,就像您之前使用 push offset Input 一样。

答案的个人主观总结

为了清楚起见,我在这里列出我认为其他用户给出的答案。随着时间的推移,我将对此进行更改,并 select 从 2019 年 8 月 8 日起的 1 周内给出答案。

  1. 您可以从字符串的 "end" 进行 SCAS 扫描。

Use lea edi, [buf + ecx - 1] to set up for std ; rep scasb. Or lea edi, [buf + ecx*2 - 2] to set up for backwards rep scasw on a buffer with ECX word elements. (Generate a point to the last element = buf + size - 1 = buf-1 size)

参考问题中的示例代码,我可以写

lea edi, [Input + ecx - 1]
std
rep scasb

第二个选项

lea edi, [Input + ecx*2 - 2]
std
rep scasw

在带有 ECX 字元素的缓冲区上给出一个向后的 rep scasw。

  1. 如果要从末尾搜索后计算相对于末尾的位置,

    you can do length - ecx or something like that. Or do pointer-subtraction on EDI.

  2. 参考MASM中的寄存器和符号定义,

    You don't "store a register in EDI", they are registers. Maybe you meant to say "pointer"? And yes, any magic that MASM does based on how you defined a symbol is completely gone once you have a pointer in a register. ASM doesn't have datatypes.

  3. 您可以通过在 musing mov.

    [=41= 之前设置方向标志来使字符串从 "end" 到 "beginning" 向后传输]
  4. 调用约定保证/要求在任何函数的进入和退出时 DF=0,因此在使用字符串指令之前不需要 cld