汇编中字符串的长度(对程序如何工作的困惑)

Posted

技术标签:

【中文标题】汇编中字符串的长度(对程序如何工作的困惑)【英文标题】:length of a string in assembly (confusion about how program works) 【发布时间】:2020-11-08 15:10:58 【问题描述】:

我有一个汇编程序,它在 ax 寄存器中写入字符串的长度,但我对一些指令有点困惑。

include \masm32\include64\masm64rt.inc
.data
string db "mama", 0        ; so here I declared a string "mama". What happens in memory?
.code
 main proc
xor ax, ax                 ; here I initialize ax with 0.
lea rsi, string            ; here, I move in rsi register the adress of string, right? But how the string is stored in memory? Now in rsi I have the adress of the first char "m" of "mama" string?
 @@:                       ; this sign creates an anonymous label
cmp byte ptr [rsi], 0      ; so this says compare 0 with with 1 byte found at the adress pointed to by rsi, right? But I still don't get it. Why 1 byte? rsi is pointing to the first char or to the whole string?
jz @F                      ; jump if zero to the nearest @@ (forward)
inc ax                     ; so now i'm pointing to the first character so ax=1
inc rsi                    ; here what happen? The pointer is incremented to point to the second char from string?
jmp @B                     ; jump to the nearest @@ (backward)
 @@:
invoke ExitProcess, 0 ; invoke ExitProcess API
ret
 main endp
end

我的困惑是我不确定我是否考虑过这个程序是如何以正确的方式工作的。我这样想对吗?

【问题讨论】:

【参考方案1】:

inc ax 将 16 位值加 1 — 处理器不知道 ax 的用途,但它知道这是一个 16 位加法。

inc rsi 将 64 位值加 1 — 处理器不“知道”这是一个整数还是指针,但它知道这是一个 64 位加法。

这个程序(虽然只是一个main 并且没有函数)类似于strlen 函数:

short strlen ( char *p ) 
    short count = 0;
    while ( *p != '\0' ) 
        count++;                // increment 16-bit counter
        p++;                    // increment 64-bit pointer by adding 1 to it
    
    return count;

处理器将指针视为整数。取消引用时引用内存位置的整数。在汇编语言中将指针加 1 使其指向内存的下一个字节——内存位置具有整数编号地址,因此两个连续的内存地址将相差 1。

请注意,由于长度计数器使用的是短的 16 位数据类型,如果字符串的实际长度 >= 32768(如果理解有符号的short),此程序将出现溢出错误或>= 65536(如果使用unsigned short 而不是short)。

【讨论】:

unsigned short 可以说是代码的更准确反映:如果它回绕,则没有未定义的行为,只是寄存器宽度的一个非常愚蠢的选择,使其无法用作没有第一个符号或零的数组索引扩展它。这在真正的 CPU 上无缘无故地效率低下,例如通过仅清除低 16 位并需要 xor 和 inc 上的操作数大小前缀来打破对 RAX 旧值的依赖性。如果您不想支持大于 4GiB 的字符串,eax 将是 100% 合理的,并且比rax 更紧凑,但ax 根本不支持。 (当然,这段代码显然不关心效率。SSE2 是 x86-64 的基线,允许我们在 L2 缓存中的热数据快 16 倍,甚至对于字节循环,这是在循环内计数而不是在之后进行指针减法。它同时具有jccjmp 而不是底部的jcc。)【参考方案2】:
string db "mama", 0

4 个字节 0x6d 0x61 0x6d 0x61 ('mama') 存储在程序内存的数据段中的某处。 string 将第一个字节的地址存储在数据段中,即'm'。

xor ax, ax

lea rsi, string

我认为操作应该是lea rsi, [string]。 (编辑:正如Peter Cordes 在下面的评论中提到的,在 MASM 汇编器中这个语法很好)

string 指向第一个字符的地址。现在rsi 指向同一个地址。

@@:                       ; this sign creates an anonymous label

cmp byte ptr [rsi], 0

rsi 指向整个字符串的开头。比较操作将 rsi 处的一个字节与零进行比较。如果为零,则假定字符串结束并跳转到退出:

jz @F                      ; jump if zero to the nearest @@ (forward)

如果 rsi 的值不为零:

inc ax

请记住,我们将字符串的长度存储在 ax 中。因此,对于每个有效字符,我们将 ax 递增 1。

inc rsi

jmp @B                     ; jump to the nearest @@ (backward)

rsi 指向下一个字符('a')并跳转到@@。第一个 @@ 之后的代码将再次检查下一个字符 ('a') 是否为零,并将计数 (ax) 加 1,因此 ax 将变为 2。这将一直持续到达到 0 , 程序假定字符串的结尾。

@@:

invoke ExitProcess, 0 ; invoke ExitProcess API

ret

 main endp

end

退出代码。

旁注:您可以使用像 gdb 这样的程序,在开始时带有断点来遍历每个步骤。使用info registers 命令,您可以检查它们的值。向 google 询问更高级的命令/方法。

【讨论】:

这是 MASM,所以 lea rsi, [string]lea rsi, string 是相同的,都是符号地址的 RIP 相对 LEA。 Confusing brackets in MASM32

以上是关于汇编中字符串的长度(对程序如何工作的困惑)的主要内容,如果未能解决你的问题,请参考以下文章

在GNU汇编程序中声明一个固定长度的填充字符串

汇编:计算字符串长度

汇编:cmp指令和寻址

通过汇编揭开String中数据结构神秘面纱

通过汇编揭开String中数据结构神秘面纱

通过汇编揭开String中数据结构神秘面纱