Вычитание двух символов - PullRequest
4 голосов
/ 01 августа 2020

Я только начал программировать на ассемблере, так что я новичок.

Для практики я пытаюсь переписать базовую c lib c в сборке (синтаксис NASM Intel).

Но я застрял на функции strcmp :

;; Compare two C-style NUL-terminated strings
;; Inputs   :  ESI = address of s1, EDI = address of s2
;; Outputs  :  EAX = return an integer less than, equal to, or greater than zero if s1 is found, respectively, to be less than, to match, or be greater than s2
strcmp:
    call strlen
    mov ecx, eax ; ecx = length of the string in esi

    repe cmpsb
    sub esi, edi ; result = *esi - *edi
    
    mov eax, esi
    
    ret

Для меня это должно работать так:

s1 db 'Hello World', 0
s2 db 'Hello Stack', 0

После repe cmpsb инструкция, ESI должно быть равно [s1 + 7] и EDI до [s2 + 7].

Так что мне просто нужно сделать EAX = 'W' - 'S' = 87 - 83 = 4

Проблема в том, что не работает. Я думаю, проблема в том, что когда я выполняю эту инструкцию:

sub esi, edi ; result = *esi - *edi

, я не думаю, что это означает: вычесть символы, на которые указывают EDI и ESI.

Кто-нибудь знает, как я могу это сделать?

1 Ответ

3 голосов
/ 01 августа 2020

Ваш код почти верен. Остались три проблемы:

  • вы не должны предполагать, что strcmp сохраняет содержимое esi и edi, если вы явно не указали, что это так. Позже очень легко изменить strcmp, а затем забыть о требовании, что приведет к разного рода неприятным проблемам.
  • вместо того, чтобы возвращать разницу между *edi и *esi, вы возвращаете разницу между edi и esi. Кроме того, поскольку cmpsb продвигает esi и edi на один, последние сравниваемые символы находятся в edi[-1] и esi[-1].
  • , у вас есть ошибка разложения: strlen возвращает количество символов, предшествующих байту NUL, но вам также необходимо сравнить байт NUL. В противном случае вы обнаружите, что две строки равны, если одна является префиксом другой, поскольку вы никогда не проверяете, заканчивается ли вторая строка, когда заканчивается первая.

Чтобы исправить первую вопрос, рекомендую сохранять и восстанавливать esi и edi вокруг звонка на strlen. Самый простой способ сделать это - поместить sh их в стек:

    push esi             ; save ESI and EDI
    push edi
    call strlen          ; compute the string length
    pop  edi             ; restore ESI and EDI
    pop  esi

Вторая проблема устраняется загрузкой символов для сравнения из памяти, вычислением разницы и последующим сохранением результата в eax:

    movzx eax, byte [esi-1] ; load byte from ESI[-1] and zero extend into EAX
    movzx ecx, byte [edi-1] ; load byte from EDI[-1] and zero extend into ECX
    sub   eax, ecx          ; compute the difference

Это также решает третью проблему за счет немедленного использования правильных смещений. Обратите внимание, что здесь требуется movzx вместо более простого

    mov   al, [esi-1]       ; load byte from ESI[-1] into AL
    sub   al, [edi-1]       ; subtract EDI[-1] from AL

, так как мы хотим, чтобы результат вычитания был правильно расширен знаком до eax.

...