cmpq比较什么?
What does cmpq compare?
mystery 有这个函数签名:
int mystery(char *, int);
这是神秘函数汇编代码:
mystery:
movl [=11=], %eax ;set eax to 0
leaq (%rdi, %rsi), %rcx ; rcx = rdi + rsi
loop:
cmpq %rdi, %rcx
jle endl
decq %rcx
cmpb [=11=]x65, (%rcx)
jne loop
incl %eax
jmp loop
endl:
ret
这一行 cmpq %rdi, %rcx
比较什么?地址还是字符值?如果它比较存储在寄存器中的地址,那有什么意义呢?如果一个地址大于另一个地址,那么?
看起来像 memrchr
,cmpq
检查搜索位置回到缓冲区的开始,cmpb
检查匹配的字节。
cmp
只是根据dst - src
设置FLAGS,和sub
一模一样。所以它当然会比较它的输入操作数。在这种情况下,它们都是保存指针的 qword 寄存器。
我不推荐jle
进行地址比较;最好将地址视为未签名。尽管对于 x86-64 它实际上并不重要;你不能有一个跨越有符号溢出边界的数组,因为那里有非规范的 "hole" 。
不过,jbe
会更有意义。除非您实际上有跨越从最高地址到最低地址边界的数组,否则指针会从 0xfff...fff
换行到 0
。但是无论如何,您可以通过 if (p == start) break
而不是 p <= start
.
来修复此错误
虽然这个函数有一个错误,假设它是为 x86-64 System V ABI 编写的:它的签名采用 int
大小的 arg,但是它char *endp = start + len
.
时假定其符号扩展为指针宽度
ABI 允许窄参数在其寄存器的高位有垃圾。
这也存在主要的性能问题:一次检查 1 个字节是总垃圾,而 SSE2 一次检查 16 个字节。此外,它不使用任何一个条件分支作为循环分支,因此每次迭代有 3 次跳转而不是 2 次。即一个额外的未采用条件分支。
此外,它在循环后进行指针减法,而不是在循环内浪费 inc %eax
。如果你打算在循环内做 inc %eax
,你最好检查它的大小而不是指针比较。
反正函数写的是为了方便逆向工程,不是为了高效。 jmp
以及 2 个条件分支使该 IMO 变得更糟,而不是在底部有条件的惯用循环。
好像是这样的:
char* buff = "abcdef" //this is the rdi.
int64_t len = strlen(buff); //this is the rsi.
for(char* pRCX = buf+len; pRCX >= buff/*this is the cmpq*/; pRCX--){
//do something.
}
代码中的cmpq
检查rcx是否到达数据数组的开始。它在每个循环中都会减少,因为它从数组中的最后一项开始。
是的,cmpq %rdi, %rcx
比较地址。似乎是循环遍历字符数组的优化版本。它不是遍历索引,而是直接遍历地址。这种方式比较快,但是有点难掌握,特别适合新手。
另外,我想我是在 agner 的书中读到的,从最后一项开始循环访问一系列数据并以降序访问比在编写循环时通常以升序访问更快。
mystery 有这个函数签名:
int mystery(char *, int);
这是神秘函数汇编代码:
mystery:
movl [=11=], %eax ;set eax to 0
leaq (%rdi, %rsi), %rcx ; rcx = rdi + rsi
loop:
cmpq %rdi, %rcx
jle endl
decq %rcx
cmpb [=11=]x65, (%rcx)
jne loop
incl %eax
jmp loop
endl:
ret
这一行 cmpq %rdi, %rcx
比较什么?地址还是字符值?如果它比较存储在寄存器中的地址,那有什么意义呢?如果一个地址大于另一个地址,那么?
看起来像 memrchr
,cmpq
检查搜索位置回到缓冲区的开始,cmpb
检查匹配的字节。
cmp
只是根据dst - src
设置FLAGS,和sub
一模一样。所以它当然会比较它的输入操作数。在这种情况下,它们都是保存指针的 qword 寄存器。
我不推荐jle
进行地址比较;最好将地址视为未签名。尽管对于 x86-64 它实际上并不重要;你不能有一个跨越有符号溢出边界的数组,因为那里有非规范的 "hole" 。
不过,jbe
会更有意义。除非您实际上有跨越从最高地址到最低地址边界的数组,否则指针会从 0xfff...fff
换行到 0
。但是无论如何,您可以通过 if (p == start) break
而不是 p <= start
.
虽然这个函数有一个错误,假设它是为 x86-64 System V ABI 编写的:它的签名采用 int
大小的 arg,但是它char *endp = start + len
.
ABI 允许窄参数在其寄存器的高位有垃圾。
这也存在主要的性能问题:一次检查 1 个字节是总垃圾,而 SSE2 一次检查 16 个字节。此外,它不使用任何一个条件分支作为循环分支,因此每次迭代有 3 次跳转而不是 2 次。即一个额外的未采用条件分支。
此外,它在循环后进行指针减法,而不是在循环内浪费 inc %eax
。如果你打算在循环内做 inc %eax
,你最好检查它的大小而不是指针比较。
反正函数写的是为了方便逆向工程,不是为了高效。 jmp
以及 2 个条件分支使该 IMO 变得更糟,而不是在底部有条件的惯用循环。
好像是这样的:
char* buff = "abcdef" //this is the rdi.
int64_t len = strlen(buff); //this is the rsi.
for(char* pRCX = buf+len; pRCX >= buff/*this is the cmpq*/; pRCX--){
//do something.
}
代码中的cmpq
检查rcx是否到达数据数组的开始。它在每个循环中都会减少,因为它从数组中的最后一项开始。
是的,cmpq %rdi, %rcx
比较地址。似乎是循环遍历字符数组的优化版本。它不是遍历索引,而是直接遍历地址。这种方式比较快,但是有点难掌握,特别适合新手。
另外,我想我是在 agner 的书中读到的,从最后一项开始循环访问一系列数据并以降序访问比在编写循环时通常以升序访问更快。