当使用 MOV 助记符将字符串加载/复制到 MASM 中的内存寄存器时,字符是否以相反的顺序存储?

Joa*_*ves 2 string x86 assembly masm masm32

我想知道使用 MOV 指令将字符串复制到寄存器中是否会导致字符串以相反的顺序存储。我了解到,当 MASM 将字符串存储到定义为单词或更高(dw 和更大尺寸)的变量中时,字符串以相反的顺序存储。当我将字符串复制到寄存器时会发生同样的事情吗?

基于这个问题(关于 SCAS 指令以及关于在 MASM 32 中为变量分配字符串和字符)我假设如下:

  1. 当 MASM 将字符串加载到变量中时,它以相反的顺序加载它,即字符串中的最后一个字符存储在字符串变量的最低内存地址(开头)中。这意味着分配一个变量 str 像这样:str dd "abc"导致 MASM 将字符串存储为“cba”,这意味着“c”位于最低的内存地址中。
  2. 将变量定义为str db "abc"MASM 时,将其str视为字符数组。尝试将数组索引与 的内存地址匹配str,MASM 将在 的最低内存地址处存储“a” str
  3. 默认情况下,SCAS 和 MOVS 指令从目标字符串(即存储在 EDI 寄存器中的字符串)的开始(最低)地址开始执行。在执行之前,它们不会“弹出”或将“后进先出”规则应用于它们操作的内存地址。
  4. MASM 始终以相同的方式将字符数组和字符串处理到内存寄存器。将字符数组 'a'、'b'、'c' 移动到 EAX 与将“abc”移动到 EAX 相同。

当我一个字节数组传递arLetters与字符“a”,“b”和“c”来的双字变量strLetters使用MOVSD,相信的字母被复制到strLetters在反向,即作为“CBA”存储。当我使用mov eax, "abc"时,字母是否也以相反的顺序存储?

下面的代码将在退出之前设置零标志。

.data?
strLetters dd ?,0

.data
arLetters db "abcd"

.code

start:
mov ecx, 4
lea esi, arLetters
lea edi, strLetters
movsd
;This stores the string "dcba" into strLetters.

mov ecx, 4
lea edi, strLetters
mov eax, "dcba" 
repnz scasd
jz close
jmp printer
;strLetters is not popped as "abcd" and is compared as "dcba".

printer:
print "No match.",13,10,0
jmp close

close:
push 0
call ExitProcess

end start
Run Code Online (Sandbox Code Playgroud)

我希望字符串“dcba”“按原样”存储在 EAX 中 - 在 EAX 的最低内存地址中带有 'd' - 因为 MASM 将字符串移动到寄存器与将字符串分配给变量不同。MASM 将 'a', 'b', 'c' 'd'" 作为 "dcba" 复制到 strLetters 中,以确保在弹出 strLetters 时,字符串以正确的顺序 ("abcd") 发出/释放。如果REP MOVSB指令被用来代替MOVSD,strLetters 将包含“abcd”并且将作为“dcba”弹出/发送。但是,MOVSD因为使用了并且 SCAS 或 MOVS 指令在执行前不会弹出字符串,所以上面的代码应该设置零标志,对?

Ros*_*dge 5

不要在 MASM 需要 16 位或更大整数的上下文中使用字符串。MASM 会将它们转换为整数,这种方式在存储在内存中时会颠倒字符的顺序。由于这令人困惑,因此最好避免这种情况,并且仅将字符串与 DB 指令一起使用,这会按预期工作。不要使用多于字符的字符串作为直接值。

内存有字节顺序,寄存器没有

寄存器没有地址,讨论寄存器内的字节顺序是没有意义的。在 32 位 x86 CPU 上,通用寄存器(如 EAX)保存 32 位整数值。您可以在概念上将 32 位值划分为 4 个字节,但是当它存在于寄存器中时,字节没有有意义的顺序。

只有当内存中存在 32 位值时,组成它们的 4 个字节才会有地址,因此才有顺序。由于 x86 CPU 使用little-endian 字节顺序,这意味着 4 个字节中的最低有效字节是第一个字节。最重要的部分成为最后一个字节。每当 x86 向内存加载或存储 16 位或更宽的值时,它使用小端字节序。(一个例外是 MOVBE 指令,它在加载和存储值时专门使用大端字节序。)

所以考虑这个程序:

    .MODEL flat

    .DATA
db_str  DB  "abcd"
dd_str  DD  "abcd"
num DD  1684234849

    .CODE
_start: 
    mov eax, "abcd"
    mov ebx, DWORD PTR [db_str]
    mov ecx, DWORD PTR [dd_str]
    mov edx, 1684234849
    mov esi, [num]
    int 3

    END _start
Run Code Online (Sandbox Code Playgroud)

组装和链接后,它被转换为字节序列,如下所示:

.text section:
  00401000: B8 64 63 62 61 8B 1D 00 30 40 00 8B 0D 04 30 40  ,dcba...0@....0@
  00401010: 00 BA 61 62 63 64 8B 35 08 30 40 00 CC           .ºabcd.5.0@.I
  ...
.data section:
  00403000: 61 62 63 64 64 63 62 61 61 62 63 64              abcddcbaabcd
Run Code Online (Sandbox Code Playgroud)

(在 Windows 上,该.data部分通常放在.text内存中的部分之后。)

DB 和 DD 对字符串的处理方式不同

所以我们可以看到 DB 和 DD 指令,标记为db_str和 的指令,dd_str为同一个字符串生成两个不同的字节序列"abcd"。在第一种情况下,产生MASM,我们将我们所期望的字节,61H,62H,63H,64H和,对于ASCII值的序列abc,和d分别。因为dd_str虽然字节序列是相反的。这是因为 DD 指令使用 32 位整数作为操作数,因此必须将字符串转换为 32 位值,而当转换结果存储在内存中时,MASM 最终会颠倒字符串中的字符顺序。

在内存中,字符串和数字都只是字节

您还会注意到标记为 DD 的指令num也生成了与 DB 指令相同的字节序列。事实上,如果不查看源代码,就无法判断前四个字节应该是字符串,而后四个字节应该是数字。如果程序以这种方式使用它们,它们只会变成字符串或数字。

(不太明显的是十进制值 1684234849 是如何被转换成与 DB 指令生成的序列字节相同的。它已经是一个 32 位的值,它只需要被 MASM 转换成一个字节序列。不出所料,汇编程序所以使用与 CPU 相同的小端字节顺序。这意味着第一个字节是 1684234849 的最低有效部分,它恰好与 ASCII 字母具有相同的值a(1684234849 % 256 = 97 = 61h)。最后一个字节是数字的最重要部分,恰好是d(1684234849 / 256 / 256 / 256 = 100 = 64h)的 ASCII 值。)

立即数像 DD 一样处理字符串

.text使用反汇编器更仔细地查看该部分中的值,我们可以看到存储在那里的字节序列在 CPU 执行时将如何解释为指令:

  00401000: B8 64 63 62 61     mov         eax,61626364h
  00401005: 8B 1D 00 30 40 00  mov         ebx,dword ptr ds:[00403000h]
  0040100B: 8B 0D 04 30 40 00  mov         ecx,dword ptr ds:[00403004h]
  00401011: BA 61 62 63 64     mov         edx,64636261h
  00401016: 8B 35 08 30 40 00  mov         esi,dword ptr ds:[00403008h]
  0040101C: CC                 int         3
Run Code Online (Sandbox Code Playgroud)

我们在这里可以看到,MASMmov eax, "abcd"以与dd_strDD 指令相同的顺序存储构成指令中立即数的字节。内存中指令的立即数部分的第一个字节是 64h,即 的 ASCII 值d。之所以会这样,是因为有一个32位的目的寄存器,这个MOV指令使用了一个32位的立即数。这意味着 MASM 需要将字符串转换为 32 位整数,并最终像处理dd_str. MASM 还以mov ecx, 1684234849与使用相同数字的 DD 指令相同的方式处理作为立即数给出的十进制数。32 位值被转换为相同的小端表示。

在内存中,指令也只是字节

您还会注意到反汇编器生成的汇编指令使用十六进制值作为这两条指令的立即数。像 CPU 一样,汇编器无法知道立即数应该是字符串和十进制数。它们只是程序中的一个字节序列,它只知道它们是 32 位立即数(来自操作码 B8h 和 B9h),因此将它们显示为 32 位十六进制值,因为没有更好的替代方案.

寄存器中的值反映内存顺序

通过在调试器下执行程序并在到达断点指令 ( int 3)后检查寄存器,我们可以看到寄存器中实际结束的内容:

eax=61626364 ebx=64636261 ecx=61626364 edx=64636261 esi=64636261 edi=00000000
eip=0040101c esp=0018ff8c ebp=0018ff94 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
image00000000_00400000+0x101c:
0040101c cc              int     3
Run Code Online (Sandbox Code Playgroud)

现在我们可以看到第一条和第三条指令加载的值与其他指令不同。这两条指令都涉及 MASM 将字符串转换为 32 位值并最终反转内存中字符顺序的情况。寄存器转储确认内存中内存中字节的颠倒顺序会导致将不同的值加载到寄存器中。

但实际上,寄存器没有字节顺序

现在您可能正在查看上面的寄存器转储,并认为只有 EAX 和 ECX 的顺序正确a,首先是61h 的 ASCII 值d,最后是64h的 ASCII 值。MASM 反转内存中字符串的顺序实际上导致它们以正确的顺序加载到寄存器中。但正如我之前所说,寄存器中没有字节顺序。数字61626364就是调试器在将值显示为您可以读取的字符序列时表示值的方式。那些角色61在调试器的表示中排在第一位,因为我们的编号系统将数字的最重要部分放在左侧,我们从左到右读取,使其成为第一部分。然而,正如我之前所说的,x86 CPU 是小端的,这意味着最不重要的部分首先出现在内存中。这意味着内存中的第一个字节成为寄存器中值的最低有效部分,调试器将其显示为数字最右边的两个十六进制数字,因为这是数字在我们的编号系统中的最低有效部分。

换句话说,因为 x86 CPU 是 little-endian,最不重要的在前,但我们的编号系统是 big-endian,最重要的在前,十六进制数字以与它们实际存储在内存中的方式相反的字节顺序显示。

简单地复制“字符串”不会改变它们的顺序

现在也应该很清楚,将字符串加载到寄存器中只是概念上发生的事情。该字符串被汇编器转换为字节序列,当加载到 32 位寄存器时,它在内存中被视为小端 32 位整数。当寄存器中的 32 位值存储在内存中时,32 位值被转换为一个字节序列,以小端格式表示该值。对于 CPU 而言,您的字符串只是一个 32 位整数,它从内存中加载和存储。

所以这意味着,如果示例程序中加载到 EAX 中的值以类似的方式存储到内存中,mov [mem], eax那么存储的 4 个字节mem的顺序将与它们出现在组成mov eax, "abcd". 这是在相同的相反顺序中,64h、63h、62h、61h,MASM 将它们放在构成立即数的字节中。

但为什么?我不知道,只是不要那样做

现在至于为什么 MASM 在将字符串转换为 32 位整数时反转字符串的顺序我不知道,但这里的道德是不要将字符串用作立即数或任何其他需要转换为整数的上下文。汇编器在如何将字符串文字转换为整数方面不一致。(类似的问题发生在 C 编译器如何将字符文字转换'abcd'为整数。)

SCSD 和 MOVSD 并不特别

SCSD 或 MOVSD 指令没有什么特别的。SCSD 将 EDI 指向的四个字节视为 32 位小端值,将其加载到未命名的临时寄存器中,将临时寄存器与 EAX 进行比较,然后根据 DF 标志从 EDI 中加或减 4。MOVSD 将 ESI 指向的内存中的 32 位值加载到未命名的临时寄存器中,将 EDI 指向的 32 位内存位置存储在临时寄存器中,然后根据 DF 标志更新 ESI 和 EDI。(字节顺序对于 MOVSD 无关紧要,因为字节从不用作 32 位值,但顺序不会改变。)

我不会尝试将 SCSD 或 MOVSD 视为 FIFO 或 LIFO,因为最终这取决于您如何使用它们。MOVSD 可以像 LIFO 堆栈一样轻松地用作 FIFO 队列实现的一部分。(将其与 PUSH 和 POP 进行比较,理论上它们可以独立用作 FIFO 或 LIFO 数据结构实现的一部分,但一起只能用于实现 LIFO 堆栈。)

  • MASM 正在反转字符常量中的字节顺序,因为它很旧。大多数较新的汇编器都没有,请参阅 https://euroassembler.eu/eadoc/#CharNumbers (2认同)