2

I am trying to implement strcmp which is a C function in assembly 64, here is my working code so far :

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 

when I try to compile it with this 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);
}

the following result is as shown below :

ft_strcmp = -14
strcmp = -14

which is the result of the subtraction of o from a : ret = 'a' - 'o' which is in decimal ascii code 97 - 111 = -14.

but when I try it with another main.c as below, I just passed strings directly to both strcmp() and ft_strcmp() rather than passing declared variables:

#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);
}

the results becomes:

ft_strcmp = -14
strcmp = -1

I searched a little bit in this wide Internet and I found some explanations about this behavior:

Why does strcmp() in a template function return a different value?

Is this the only return value for strcmp() in C?

but the question is how I implement this behavior in my assembly code, I mean is there a way to know if the string is passed directly to the parameters, or not?

I tried to debug a little bit with lldb and I found the addresses of rdi and rsi (the registers that get the first parameter and the second parameter respectively) are different in the above two cases.

in the first case the addresses are written like this :

rdi = 0x00007fffffffde50  ; the address of the first string
rsi = 0x00007fffffffde70  ; the address of the second string

but int the second case they are written like this:

rdi = 0x0000555555556010  ; the address of the first string
rsi = 0x0000555555556004  ; the address of the second string

I am not sure if this will help or not, but who knows, and thanks in advance.

#Edit

Well since my question marked as [duplicate], I will post my answer that it seems to do the job of the above behavior, and it is as follows:

after debugging using lldb I noticed whenever I pass a literal string to ft_strcmp(), the address of rdi and rsi are written like this:

rdi = 0x0000555555556010  ; the address of the first string
rsi = 0x0000555555556004  ; the address of the second string

and whenever I pass declared variables instead of literal strings, the addresses become like this:

rdi = 0x00007fffffffde50  ; the address of the first string
rsi = 0x00007fffffffde70  ; the address of the second string

"at least this is what I got on my machine with linux X64 operating system", so I thought about doing some shifting tricks:

this is how 0x00007fffffffde50 is represented in binary:

 11111111111111111111111111111111101111001010000

I will shift it with 44 bits in order to get that 7 to use it in comparison later, let's store it in rax register in this example:

mov rax, 0x00007fffffffde50
rax >> 44  in assembly ==> shr  rax, 44 ==> (rax = 111 ==> 7)

and now I will check if rdi and rsi are literal strings or not :

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

and here is my final code, but I am not sure if this is a good way or not, it is just a small trick that it works with me with the above tests, not sure about complicated test. (NOTE: It will not work with calling variables that declared at the global scope, thanks to Peter Cordes for spotting out that)

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

and now the results are the same when I compile with the both main.c above.

Holy semicolon
  • 868
  • 1
  • 11
  • 27
  • 1
    This sounds like an [XY problem](https://en.wikipedia.org/wiki/XY_problem). Why do you think you need this exact behavior? – tkausl Dec 10 '20 at 19:16
  • @tkausl for knowledge purposes, my code is working perfectly I just want it to behave the same as the real one. – Holy semicolon Dec 10 '20 at 19:22
  • 1
    OMG you tested and debugged it! That is so shockingly rare that I dropped my coffee cup. Have an upvote just for that:) – Martin James Dec 10 '20 at 19:27
  • @MartinJames I appreciate that a lot. – Holy semicolon Dec 10 '20 at 19:29
  • 1
    Note that `movsx rax, r13b` after a byte subtraction is not correct for all cases. You need to zero-extend the *inputs* before subtracting, to make signed overflow impossible. [Subtracting two characters](https://stackoverflow.com/q/63204722) For the same reason that `jg` doesn't just check SF, it checks SF and OF. – Peter Cordes Dec 10 '20 at 20:49
  • 3
    If a glibc function is called *at all*, the compiler didn't inline `strcmp` and didn't evaluate the result at compile time. [Inconsistent strcmp() return value when passing strings as pointers or as literals](https://stackoverflow.com/q/27751221) is a potential duplicate. If you want to stop gcc from ever inlining `strcmp`, use `gcc -fno-builtin-strcmp`. – Peter Cordes Dec 10 '20 at 20:50
  • I have to agree. That's a duplicate. – Joshua Dec 10 '20 at 21:51
  • 1
    You're detecting locals on the stack vs. static storage based on their address. This is very fragile, and won't always match the compiler. e.g. you can have non-const strings that aren't on the stack!! such as `char str1[] = "foo";` at global scope. If you're trying to understand what's happening in the GCC version, you should look at its asm. The ways you're inventing to match its results are insane and something you would never want to do in real life, as well as only happening to give the right result in *this* case, not in general. – Peter Cordes Dec 11 '20 at 13:44
  • 1
    Your loop2 is buggy: it can only return -1 or +1, never 0, so it fails if the strings are equal! And your loop1 is still buggy: `movsx rax, r13b` to sign-extended the possibly-overflowing 8-bit sub result will fail on signed-overflow: possible if you have any high-ASCII bytes. Also, it's redundant to check both inputs against 0. Checking 1 against 0 and then checking them against each other will make sure you can't read past a zero in either string. Either they both end with a zero, or they're unequal. Also, use the `mov` load result to compare against, instead of another mem ref. – Peter Cordes Dec 11 '20 at 13:48
  • @PeterCordes thanks a lot, I will take your advice. – Holy semicolon Dec 11 '20 at 14:00

1 Answers1

4

strcmp() only guarantees the sign of the result. Something probably got optimized in the second case. You don't need to care that the magnitude is different, so it would be best if you didn't.

The compiler would be within its rights to optimize

printf("strcmp = %d\n",strcmp("hella world", "hello world"));

to

printf("strcmp = %d\n",-1);
Joshua
  • 40,822
  • 8
  • 72
  • 132