Я пытаюсь реализовать 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
выше.
@tkausl для ознакомления, мой код работает отлично, я просто хочу, чтобы он вел себя так же, как настоящий.
OMG, вы протестировали и отладили его! Это настолько поразительно редко, что я уронил свою кофейную чашку. Только за это проголосуйте :)
@MartinJames Я очень ценю это.
Обратите внимание, что movsx rax, r13b
после вычитания байта не во всех случаях верно. Перед вычитанием необходимо обнулить входные данные, чтобы сделать невозможным переполнение со знаком. Вычитание двух символов По той же причине, по которой jg
проверяет не только SF, но и SF и OF.
Если функция glibc вообще вызывается, компилятор не встраивал strcmp
и не оценивал результат во время компиляции. Несогласованное возвращаемое значение strcmp() при передаче строк в виде указателей или литералов является потенциальным дубликатом. Если вы хотите, чтобы gcc никогда не встраивал strcmp
, используйте gcc -fno-builtin-strcmp
.
Я должен согласиться. Это дубликат.
Вы определяете локальные объекты в стеке по сравнению со статическим хранилищем на основе их адреса. Это очень хрупко и не всегда будет соответствовать компилятору. например у вас могут быть неконстантные строки, которых нет в стеке!! например char str1[] = "foo";
в глобальном масштабе. Если вы пытаетесь понять, что происходит в версии GCC, вам следует взглянуть на ее asm. Способы, которые вы изобретаете, чтобы соответствовать его результатам, безумны, и вы никогда не хотели бы делать это в реальной жизни, а также давать правильный результат только в этом случае, а не в целом.
Ваш loop2 глючит: он может возвращать только -1 или +1, но не 0, поэтому он не работает, если строки равны! И ваш loop1 все еще глючит: movsx rax, r13b
для расширения знака возможно переполнение 8-битного подрезультата не удастся выполнить при подписанном переполнении: возможно, если у вас есть какие-либо байты с высоким ASCII. Кроме того, избыточно проверять оба ввода на 0. Проверка 1 на 0, а затем проверка их друг на друга гарантирует, что вы не сможете прочитать дальше нуля в любой строке. Либо они оба заканчиваются нулем, либо они не равны. Кроме того, используйте результат загрузки mov
для сравнения вместо другой ссылки на память.
@PeterCordes большое спасибо, я последую вашему совету.
strcmp()
гарантирует только знак результата. Вероятно, во втором случае что-то оптимизировали. Вам не нужно заботиться о том, чтобы величина была другой, поэтому было бы лучше, если бы вы этого не делали.
Компилятор имеет право оптимизировать
printf("strcmp = %d\n",strcmp("hella world", "hello world"));
к
printf("strcmp = %d\n",-1);
Это звучит как проблема XY. Как вы думаете, зачем вам нужно именно это поведение?