1

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

我正在使用 MASM 32 SDK - 不知道是什么版本,我通过 Visual MASM 的下载和安装向导安装了 - 使用 Visual MASM 和 MASM32 编辑器将它们链接并构建到对象和可执行文件中。我使用的是 Windows 7 Pro 64 位操作系统。

SCAS

  1. SCAS 指令“将 AL 中的字节或 AX 中的字与 ES 中的 DI 指向的字节或字进行比较”。因此,要使用 SCAS,必须将目标字符串地址移动到 EDI,并且必须将要查找的字符串移动到累加器寄存器(EAX 和变体)。

  2. 在使用 32 位系统时,设置方向标志然后使用 SCAS 将停止 SCAS 运行。在 32 位系统上,不可能强制 SCAS“从头到尾扫描字符串”。

  3. 任何 REP 指令始终使用 ECX 寄存器作为计数器,并且始终递减 ECX,而不管方向标志的值如何。这意味着使用 REP SCAS 不可能“从头到尾扫描字符串”。

资料来源:
SCAS/SCASB/SCASW,Birla Institute of Technology and Science
扫描字符串,来自 c9xm.me
SCAS/SCASB/SCASW/SCASD — 扫描字符串,来自 felixcloutier.com
MASM:使用“字符串”说明,来自 www.dreamincode.net /论坛

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

;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. “搜索”部分下的代码一次搜索字符串 InputEnd 4 个字节,因此一次搜索 4 个字符。该块扫描EAX中的字符,即单词“省略”,总是从edi中的内存地址的值开始,然后根据SCAS的后缀(B,W,D,Q)递增(MASM:使用'String ' 说明,dream-in-code.com)

MOVS

  1. 使用“移动最后一个单词”部分,我可以从字符串 Input 中获取最后一个字节。然后我使用 MOVSW 将字符串 Input 的最后 4 个字节移动到 InputEnd,假设方向标志是明确的。我必须将 Input 定义为一个字节数组Input db 32 dup(?)- 才能使块工作。

  2. 无论我如何定义 InputEnd(无论是“dd ?, 0”还是“db 12 dup(?)”),mov 和 scas 指令的操作(标志设置、寄存器修改等)都不会改变。SCAS 和 MOV 的递增/递减量取决于命令的后缀/最后一个字母,而不是存储在 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 文本是否表明网站信息错误?

4

2 回答 2

4

首先,repe/repne scas并不cmps快。此外,“快速字符串” /ERMSB 微码rep movsrep stos 在 DF=0(正常/转发/增加地址)时才快速。

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


您链接的文档准确说明了 DF 如何movs以及scas受其影响。 阅读英特尔手册中的操作部分。

请注意,它始终是后增量/减量,因此比较的第一个元素不依赖于 DF,仅依赖于 EDI 和/或 ESI 的更新。

您的代码仅依赖于 DF 的repne scasw. 增加 (DF=0) 或减少 (DF=1) EDI都没有关系,movsb因为您在下次使用之前会覆盖 EDI。


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

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

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

(或者最好使用 SSE2 SIMDpcmpeqd来实现memmem4 字节搜索“needle”。查看像 glibc 这样的优化实现以获得想法,或 strstr 实现,但在“haystack”中检查0终止符。)

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


在 32 位系统上,不可能强制 SCAS“从头到尾扫描字符串”。

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

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

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

任何 REP 指令始终使用 ECX 寄存器作为计数器,并且始终递减 ECX,而不管方向标志的值如何。这意味着使用 REP SCAS 不可能“从头到尾扫描字符串”。

这只是零意义。当然会递减;ECX=0 是搜索在不匹配时结束的方式。如果想在从末端搜索后计算相对于末端的位置,您可以执行length - ecx或类似的操作。或者在 EDI 上做指针减法。

6:不是EDI和ESI中存放的寄存器的数据类型。

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

但是,是的,一旦您在寄存器中有指针,MASM 基于您如何定义符号所做的任何魔术都将完全消失。

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

7:不能让MOVS从一个字符串的开头转移到结尾。... std

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

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

这是正确的。与使用或在其中一个指针上的普通循环相比,lods///循环是不值得的。您可以用于读取部分并手动向后写入。通过加载 dword 并在寄存器中使用将其反转,您可以将速度提高 4 倍,因此您复制的是 4 个反转字节的块。stdstosclddecsublodsbswap

或者就地反转:2 次加载到 tmp regs,然后 2 次存储,然后将指针相互移动,直到它们交叉。(也适用于bswapor movbe


代码中其他奇怪的低效率:

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

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

于 2019-08-01T06:13:09.100 回答
0

答案的个人主观总结

在这里,我将列出我认为其他用户给出的答案,只是为了清楚起见。我会随着时间的推移改变这一点,并在 2019 年 8 月 8 日起的 1 周内选择一个答案。

  1. 您可以从字符串的“结尾”进行 SCAS 扫描。

使用 lea edi, [buf + ecx - 1] 设置 std ;代表scasb。或者 lea edi, [buf + ecx*2 - 2] 在带有 ECX 字元素的缓冲区上设置后向 rep scasw。(生成指向最后一个元素的点 = 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. 如果想在从末端搜索后计算相对于末端的位置,

    你可以做长度 - ecx 或类似的东西。或者在 EDI 上做指针减法。

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

    您不会“在 EDI 中存储寄存器”,它们是寄存器。也许你的意思是说“指针”?是的,一旦你在寄存器中有一个指针,MASM 根据你定义符号的方式所做的任何魔法都完全消失了。ASM 没有数据类型。

  3. 您可以通过在考虑 mov 之前设置方向标志来使 std 向后传输,从字符串的“结束”到“开始”。

  4. cld调用约定保证/要求 DF=0 进入和退出任何函数,因此在使用字符串指令之前不需要 a 。

于 2019-08-01T09:18:13.843 回答