【发布时间】:2021-03-22 05:59:59
【问题描述】:
我正在尝试在程序集 64 中实现 strcmp,这是一个 C 函数,这是我目前的工作代码:
global ft_strcmp
section .text
ft_strcmp: ;rax ft_strcmp(rdi, rsi)
mov r12, 0
loop:
mov r13b, [rdi + r12]
cmp byte [rdi + r12], 0
jz exit
cmp byte [rsi + r12], 0
jz exit
cmp r13b, byte [rsi + r12]
jnz exit
inc r12
jmp loop
exit:
sub r13b, [rsi + r12]
movsx rax, r13b
ret
当我尝试用这个 main.c 编译它时:
#include <stdio.h>
#include <string.h>
int ft_strcmp(const char *str1, const char *str2);
int main()
{
const char str1[20] = "hella world";
const char str2[20] = "hello world";
printf("ft_strcmp = %d\n",ft_strcmp(str1, str2));
printf("strcmp = %d\n",strcmp(str1, str2));
return (0);
}
结果如下图:
ft_strcmp = -14
strcmp = -14
这是从a 中减去o 的结果:ret = 'a' - 'o' 是十进制ascii 代码97 - 111 = -14。
但是当我尝试使用另一个main.c 时,我只是将字符串直接传递给strcmp() 和ft_strcmp(),而不是传递声明的变量:
#include <stdio.h>
#include <string.h>
int ft_strcmp(const char *str1, const char *str2);
int main()
{
printf("ft_strcmp = %d\n",ft_strcmp("hella world", "hello world"));
printf("strcmp = %d\n",strcmp("hella world", "hello world"));
return (0);
}
结果变成:
ft_strcmp = -14
strcmp = -1
我在这个广阔的互联网上搜索了一下,发现了一些关于这种行为的解释:
Why does strcmp() in a template function return a different value?
Is this the only return value for strcmp() in C?
但问题是我如何在我的汇编代码中实现这种行为,我的意思是有没有办法知道字符串是否直接传递给参数?
我尝试用lldb调试了一下,发现上面两种情况rdi和rsi(the registers that get the first parameter and the second parameter respectively)的地址是不同的。
在第一种情况下,地址是这样写的:
rdi = 0x00007fffffffde50 ; the address of the first string
rsi = 0x00007fffffffde70 ; the address of the second string
但是在第二种情况下,它们是这样写的:
rdi = 0x0000555555556010 ; the address of the first string
rsi = 0x0000555555556004 ; the address of the second string
我不确定这是否会有所帮助,但谁知道呢,在此先感谢。
#编辑
好吧,既然我的问题被标记为[重复],我将发布我的答案,它似乎可以完成上述行为,如下所示:
在使用lldb 调试后,我注意到每当我将文字字符串传递给ft_strcmp() 时,rdi 和rsi 的地址是这样写的:
rdi = 0x0000555555556010 ; the address of the first string
rsi = 0x0000555555556004 ; the address of the second string
每当我传递声明的变量而不是文字字符串时,地址就会变成这样:
rdi = 0x00007fffffffde50 ; the address of the first string
rsi = 0x00007fffffffde70 ; the address of the second string
“至少这是我在linux X64操作系统的机器上得到的”,所以我想到了做一些转换技巧:
0x00007fffffffde50 是这样用二进制表示的:
11111111111111111111111111111111101111001010000
为了让7稍后在比较中使用它,我将它移位44位,让我们将它存储在这个例子中的rax寄存器中:
mov rax, 0x00007fffffffde50
rax >> 44 in assembly ==> shr rax, 44 ==> (rax = 111 ==> 7)
现在我将检查 rdi 和 rsi 是否是文字字符串:
mov r8, rdi ; store the address of rdi in r8
shr r8, 44 ; right shift the address of r8 by 44 bits
cmp r8, rax ; compare if the results are the same or not
jl loop2 ; if r8 < rax then jump to loop2 for example 5 < 7
这是我的最终代码,但我不确定这是否是一个好方法,这只是一个小技巧,它适用于我的上述测试,不确定复杂的测试。 (注意:它不适用于调用在全局范围内声明的变量,感谢 Peter Cordes 发现这一点)
global ft_strcmp
section .text
ft_strcmp: ;rax ft_strcmp(rdi, rsi)
mov r12, 0
mov rax, 0x00007fffffffde50
shr rax, 44
mov r8, rdi
shr r8, 44
cmp r8, rax
jl loop2
loop1:
mov r13b, [rdi + r12]
cmp byte [rdi + r12], 0
jz exit1
cmp byte [rsi + r12], 0
jz exit1
cmp r13b, byte [rsi + r12]
jnz exit1
inc r12
jmp loop1
exit1:
sub r13b, [rsi + r12]
movsx rax, r13b
ret
loop2:
mov r13b, [rdi + r12]
cmp byte [rdi + r12], 0
jz exit2
cmp byte [rsi + r12], 0
jz exit2
cmp r13b, byte [rsi + r12]
jnz exit2
inc r12
jmp loop2
exit2:
cmp r13b, byte [rsi + r12]
jl ret_m
jg ret_p
ret_z:
mov rax, 0
ret
ret_p:
mov rax, 1
ret
ret_m:
mov rax, -1
ret
现在当我用上面的main.c 编译时结果是一样的。
【问题讨论】:
-
这听起来像XY problem。为什么你认为你需要这种确切的行为?
-
天啊,你测试并调试好了!这种情况非常罕见,以至于我掉了咖啡杯。对此投赞成票:)
-
请注意,字节减法后的
movsx rax, r13b并非对所有情况都正确。您需要在减去之前对 inputs 进行零扩展,以使有符号溢出不可能。 Subtracting two characters 与jg不只是检查 SF,它检查 SF 和 OF 的原因相同。 -
如果一个 glibc 函数被调用根本,编译器没有内联
strcmp并且没有在编译时评估结果。 Inconsistent strcmp() return value when passing strings as pointers or as literals 可能是重复的。如果您想阻止 gcc 永远内联strcmp,请使用gcc -fno-builtin-strcmp。 -
您正在根据地址检测堆栈上的本地人与静态存储。这是非常脆弱的,并不总是与编译器匹配。例如你可以有不在堆栈上的非常量字符串!比如
char str1[] = "foo";在全局范围内。如果您想了解 GCC 版本中发生了什么,您应该查看它的 asm。你发明的方法来匹配它的结果是疯狂的,这是你在现实生活中永远不想做的事情,而且只是碰巧在 this 的情况下给出了正确的结果,而不是一般情况下。
标签: c linux assembly nasm strcmp