我不懂strcmp的编译器优化,针对常量字符串

我不懂strcmp的编译器优化,针对常量字符串,c,gcc,assembly,x86,strcmp,C,Gcc,Assembly,X86,Strcmp,为了提高我的二进制开发技能,加深对低级环境的理解,我尝试在pwnable.kr中解决挑战,第一个挑战名为fd,它有以下C代码: #include <stdio.h> #include <stdlib.h> #include <string.h> char buf[32]; int main(int argc, char* argv[], char* envp[]){ if(argc<2){ printf("

为了提高我的二进制开发技能,加深对低级环境的理解,我尝试在pwnable.kr中解决挑战,第一个挑战名为fd,它有以下C代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char buf[32];
int main(int argc, char* argv[], char* envp[]){
        if(argc<2){
                printf("pass argv[1] a number\n");
                return 0;
        }
        int fd = atoi( argv[1] ) - 0x1234;
        int len = 0;
        len = read(fd, buf, 32);
        if(!strcmp("LETMEWIN\n", buf)){
                printf("good job :)\n");
                system("/bin/cat flag");
                exit(0);
        }
        printf("learn about Linux file IO\n");
        return 0;

}
我不明白的是:

strcmp电话在哪里?为什么会这样? 这个8048506:b9 0a 00 00 mov ecx,0xa是做什么的? 编译器使用实现memcmp的已知长度字符串内联strcmp

它将常量文本字符串LETMEWIN的地址加载到寄存器esi中\n。请注意,此字符串的长度为10,结尾为“\0”。 然后将buf的地址加载到edi寄存器中,然后调用x86指令:

repz cmps BYTE PTR ds:[esi],BYTE PTR es:[edi]
只要设置了零标志并达到ecx中存储的次数,就重复以下指令。这说明了mov ecx,0xa;这是什么

重复指令是一个字节一个字节地比较字符串,并在每次迭代时自动将指针增加1。 当比较的字节相等时,它设置零标志

因此,根据您的问题:

strcmp电话在哪里?为什么会这样

没有对strcmp的显式调用,它被优化并替换为内联代码:

 80484fc:   ba 46 86 04 08          mov    edx,0x8048646 ; "LETMEWIN\n" address
 8048501:   b8 60 a0 04 08          mov    eax,0x804a060 ; buf address
 8048506:   b9 0a 00 00 00          mov    ecx,0xa ; number of bytes to compare
 804850b:   89 d6                   mov    esi,edx
 804850d:   89 c7                   mov    edi,eax
 804850f:   f3 a6                   repz cmps BYTE PTR ds:[esi],BYTE PTR es:[edi] ;
实际上,它遗漏了应该检查strcmp返回值是否为零的部分。我想你只是没有抄到这里。可能应该有类似je…/jz…/jne…/jnz。。。就在repz之后。。。线路

这个8048506:b9 0a 00 00 mov ecx,0xa是做什么的

它设置要比较的最大字节数。

编译器内联strcmp与实现memcmp的已知长度字符串进行比较

它将常量文本字符串LETMEWIN的地址加载到寄存器esi中\n。请注意,此字符串的长度为10,结尾为“\0”。 然后将buf的地址加载到edi寄存器中,然后调用x86指令:

repz cmps BYTE PTR ds:[esi],BYTE PTR es:[edi]
只要设置了零标志并达到ecx中存储的次数,就重复以下指令。这说明了mov ecx,0xa;这是什么

重复指令是一个字节一个字节地比较字符串,并在每次迭代时自动将指针增加1。 当比较的字节相等时,它设置零标志

因此,根据您的问题:

strcmp电话在哪里?为什么会这样

没有对strcmp的显式调用,它被优化并替换为内联代码:

 80484fc:   ba 46 86 04 08          mov    edx,0x8048646 ; "LETMEWIN\n" address
 8048501:   b8 60 a0 04 08          mov    eax,0x804a060 ; buf address
 8048506:   b9 0a 00 00 00          mov    ecx,0xa ; number of bytes to compare
 804850b:   89 d6                   mov    esi,edx
 804850d:   89 c7                   mov    edi,eax
 804850f:   f3 a6                   repz cmps BYTE PTR ds:[esi],BYTE PTR es:[edi] ;
实际上,它遗漏了应该检查strcmp返回值是否为零的部分。我想你只是没有抄到这里。可能应该有类似je…/jz…/jne…/jnz。。。就在repz之后。。。线路

这个8048506:b9 0a 00 00 mov ecx,0xa是做什么的


它设置了要比较的最大字节数。

@alinsoar:但这里并不是这样。这是一个常规的内联字符串比较。对于OP:移动到ecx的10是字符串比较的重复计数。repz cmps是比较字符串的内容。请在英特尔手册中查找!如果使用最新版本编译,应该尝试gcc-O2-S-Wall-fverbose asm src.c,然后查看生成的汇编代码src。s@alinsoar当前位置尽管如此,这里发生的事情却并非如此。这是一个常规的内联字符串比较。对于OP:移动到ecx的10是字符串比较的重复计数。repz cmps是比较字符串的内容。请在英特尔手册中查找!如果您使用最新版本进行编译,您应该尝试gcc-O2-S-Wall-fverbose asm src.c,然后查看生成的汇编代码src.sFun事实:repe cmpsb速度不快,而且这种strcmp策略也不太好。gcc甚至曾经将strlen内联为repne scasb at-O1,这导致了在很长字符串上使用它的代码的巨大性能下降:@PeterCordes right。。。在32位或64位系统上逐字节比较内存一点也不高效,但是对于本例中的10字节字符串来说,这可能足够了。有更好的方法,例如,在4字节块中比较3x cmp immediate/jne。但这有更多的总分支。或者两个未对齐的movq xmm0,[eax]加载分别与缓冲区的开始和结束对齐。或者movq/movhps将两个重叠的8字节加载到一个寄存器中,用于针对16字节常量的pcmpeqb。如果您知道可以覆盖源代码而不存在跨入未映射页面和出错的风险,那么SSE4.2 pcmpistri非常有用。但在这种情况下,您也可以只使用pcmpeqb/pmovmskb,去掉您不关心的位。e、 g.和eax,1@PeterCordes肯定然而,它需要目标CPU来支持SSE4.2,我不知道它是用什么CPU标志编译的。。。另一方面,即使它是用这样的标志编译的,我怀疑GCC会产生任何类似于您所建议的东西
egy不太好。gcc甚至曾经将strlen内联为repne scasb at-O1,这导致了在很长字符串上使用它的代码的巨大性能下降:@PeterCordes right。。。在32位或64位系统上逐字节比较内存一点也不高效,但是对于本例中的10字节字符串来说,这可能足够了。有更好的方法,例如,在4字节块中比较3x cmp immediate/jne。但这有更多的总分支。或者两个未对齐的movq xmm0,[eax]加载分别与缓冲区的开始和结束对齐。或者movq/movhps将两个重叠的8字节加载到一个寄存器中,用于针对16字节常量的pcmpeqb。如果您知道可以覆盖源代码而不存在跨入未映射页面和出错的风险,那么SSE4.2 pcmpistri非常有用。但在这种情况下,您也可以只使用pcmpeqb/pmovmskb,去掉您不关心的位。e、 g.和eax,1@PeterCordes肯定然而,它需要目标CPU来支持SSE4.2,我不知道它是用什么CPU标志编译的。。。另一方面,即使它是用这样的标志编译的,我怀疑GCC会产生任何类似于您所建议的东西。