Реализовать функцию strcmp в сборке 64 на Linux

Я пытаюсь реализовать strcmp, которая является C функцией в сборке 64, вот мой рабочий код:

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

который является результатом вычитания o из a : 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

Я немного поискал в этом широком интернете и нашел несколько объяснений такому поведению:

Почему strcmp() в функции шаблона возвращает другое значение?

Это единственное возвращаемое значение для strcmp() в 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

Я сдвину его на 44 бита, чтобы получить это 7 для использования в сравнении позже, давайте сохраним его в регистре 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

и вот мой окончательный код, но я не уверен, хороший это способ или нет, это просто небольшой трюк, который работает со мной с вышеуказанными тестами, не уверен насчет сложного теста. (ПРИМЕЧАНИЕ. Это не будет работать с вызовом переменных, объявленных в глобальной области видимости, спасибо Питеру Кордесу за то, что он это заметил)

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. Как вы думаете, зачем вам нужно именно это поведение?

tkausl 10.12.2020 20:16

@tkausl для ознакомления, мой код работает отлично, я просто хочу, чтобы он вел себя так же, как настоящий.

Holy semicolon 10.12.2020 20:22

OMG, вы протестировали и отладили его! Это настолько поразительно редко, что я уронил свою кофейную чашку. Только за это проголосуйте :)

Martin James 10.12.2020 20:27

@MartinJames Я очень ценю это.

Holy semicolon 10.12.2020 20:29

Обратите внимание, что movsx rax, r13b после вычитания байта не во всех случаях верно. Перед вычитанием необходимо обнулить входные данные, чтобы сделать невозможным переполнение со знаком. Вычитание двух символов По той же причине, по которой jg проверяет не только SF, но и SF и OF.

Peter Cordes 10.12.2020 21:49

Если функция glibc вообще вызывается, компилятор не встраивал strcmp и не оценивал результат во время компиляции. Несогласованное возвращаемое значение strcmp() при передаче строк в виде указателей или литералов является потенциальным дубликатом. Если вы хотите, чтобы gcc никогда не встраивал strcmp, используйте gcc -fno-builtin-strcmp.

Peter Cordes 10.12.2020 21:50

Я должен согласиться. Это дубликат.

Joshua 10.12.2020 22:51

Вы определяете локальные объекты в стеке по сравнению со статическим хранилищем на основе их адреса. Это очень хрупко и не всегда будет соответствовать компилятору. например у вас могут быть неконстантные строки, которых нет в стеке!! например char str1[] = "foo"; в глобальном масштабе. Если вы пытаетесь понять, что происходит в версии GCC, вам следует взглянуть на ее asm. Способы, которые вы изобретаете, чтобы соответствовать его результатам, безумны, и вы никогда не хотели бы делать это в реальной жизни, а также давать правильный результат только в этом случае, а не в целом.

Peter Cordes 11.12.2020 14:44

Ваш loop2 глючит: он может возвращать только -1 или +1, но не 0, поэтому он не работает, если строки равны! И ваш loop1 все еще глючит: movsx rax, r13b для расширения знака возможно переполнение 8-битного подрезультата не удастся выполнить при подписанном переполнении: возможно, если у вас есть какие-либо байты с высоким ASCII. Кроме того, избыточно проверять оба ввода на 0. Проверка 1 на 0, а затем проверка их друг на друга гарантирует, что вы не сможете прочитать дальше нуля в любой строке. Либо они оба заканчиваются нулем, либо они не равны. Кроме того, используйте результат загрузки mov для сравнения вместо другой ссылки на память.

Peter Cordes 11.12.2020 14:48

@PeterCordes большое спасибо, я последую вашему совету.

Holy semicolon 11.12.2020 15:00
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
10
573
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

strcmp() гарантирует только знак результата. Вероятно, во втором случае что-то оптимизировали. Вам не нужно заботиться о том, чтобы величина была другой, поэтому было бы лучше, если бы вы этого не делали.

Компилятор имеет право оптимизировать

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

к

printf("strcmp = %d\n",-1);

Другие вопросы по теме