Как я могу сравнить первый символ строки с другим символом в сборке x86-64? - PullRequest
0 голосов
/ 24 января 2019

У меня есть инициализированная строка "Hello, World!"из которого я хотел бы извлечь первый символ (то есть 'H') и сопоставить его с символом, который передается в регистр во время выполнения.

Я попытался сравнить первый символ "Hello, World!"с 'H' через следующий код:

global start

section .data
msg: db "Hello, World!", 10, 0

section .text
start:
   mov rdx, msg
   mov rdi, [rdx]
   mov rsi, 'H'
   cmp rdi, rsi
   je equal

   mov rax, 0x2000001
   mov rdi, [rdx]
   syscall

equal:
   mov rax, 0x2000001
   mov rdi, 58
   syscall

Однако этот код завершается без перехода к метке equal.Кроме того, состояние выхода моей программы 72, что является кодом ASCII для H.Это заставило меня попытаться передать 72 в rsi вместо H, но это также привело к тому, что программа завершает работу без перехода к метке equal.

Как правильно сравнить первоеперсонаж в "Привет, мир!"с символом, который передается в регистр?

Ответы [ 2 ]

0 голосов
/ 24 января 2019

Вы и ответ @ Rafael чрезвычайно усложняете ваш код.

Обычно вы никогда не захотите использовать mov rdi, msg с 64-битным непосредственным значением абсолютного адреса.(См. 64-разрядный формат Mach-O не поддерживает 32-разрядные абсолютные адреса. NASM Accessing Array )

Используйте default rel и используйте cmp byte [msg], 'H'.Или, если вам нужен указатель в RDI, чтобы вы могли увеличивать его в цикле, используйте lea rdi, [rel msg].

Единственное, что отличается между вашими ветвями, это значение RDI.Вам не нужно дублировать настройку RAX или syscall, просто получите правильное значение в RDI и затем ветки соединятся друг с другом.(Или делайте это без ответвлений.)

@ Ответ Рафаэля по какой-то причине все еще загружает 8 байтов из строки, как и обе загрузки в вашем вопросе.Предположительно, это sys_exit, и он игнорирует старшие байты, устанавливая только состояние выхода процесса из младшего байта, но просто для забавы, давайте представим, что мы действительно хотим, чтобы все 8 байтов были загружены для системного вызова, сравнивая только младший байт.

default rel         ; use RIP-relative addressing modes by default for [label]
global start

section .rodata                       ;; read-only data usually belongs in .rodata
msg: db "Hello, World!", 10, 0

section .text
start:
   mov   rdi, [msg]    ; 8 byte load from a RIP-relative address
   mov   ecx, 'H'

   cmp   dil, cl       ; compare the low byte of RDI (dil) with the low byte of RCX (cl)
   jne   .notequal
   ;; fall through on equal
   mov   edi, 58
.notequal:             ; .labels are local labels in NASM

   ; mov rdi, [rdx]    ; still loaded from before; we didn't destroy it.
   mov eax, 0x2000001
   syscall

По возможности избегайте записи в AH / BH / CH / DH.Он либо имеет ложную зависимость от старого значения RAX / RBX / RCX / RDX, либо может вызвать срыв частичных регистров при последующем чтении полного регистра.Ответ @ Рафаэля не делает этого, но mov ah, 'H' зависит от загрузки в AL на некоторых процессорах.См. Почему GCC не использует частичные регистры? и Как именно работают частичные регистры в Haswell / Skylake?Написание AL, похоже, ложно зависит от RAX, а AH несовместимо - mov ah, 'H' имеет ложную зависимость от старого значения AH в Haswell / Skylake, хотя AH переименовывается отдельно от RAX.Но AL не, так что да, это вполне может иметь ложную зависимость от нагрузки, мешая ей работать параллельно и задерживая cmp на цикл.

В любом случае, TL: DR здесьчто вы не должны возиться с написанием AH / BH / CH / DH, если вам не нужно.Чтение их часто хорошо, но может иметь худшую задержку.И обратите внимание, что cmp dil, ah не кодируется, потому что DIL доступен только с префиксом REX, а AH доступен только без.

Я выбрал RCX вместо RSI, потому что CL не нужен префикс REX, нотак как нам нужно взглянуть на младший байт RDI (dil), нам все равно нужен префикс REX для cmp.Я мог бы использовать mov cl, 'H' для сохранения размера кода, потому что, вероятно, нет проблемы с ложной зависимостью от старого значения RCX.


Кстати, cmp dil, 'H' будет работать так же хорошо, как и cmp dil, cl.

Или, если мы загружаем байт с нулевым расширением в полный RDI, мы можем использовать cmp edi, 'H' вместо его младшей версии.( Расширяющиеся с нуля нагрузки являются нормальным / рекомендуемым способом работы с байтами и 16-разрядными целыми числами на современном x86-64. Слияние с младшим байтом старого значения регистра обычно хуже для производительности, котораяпричина Почему инструкции x86-64 для 32-битных регистров обнуляют верхнюю часть полного 64-битного регистра? .)

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

Версия 2, фактически загружает только 1 байт:

start:
   movzx   edi, byte [msg]    ; 1 byte load, zero extended to 4 (and implicitly to 8)

   mov     eax, 58            ; ASCII ':'
   cmp     edi, 'H'
   cmove   edi, eax           ; edi =  (edi == 'H') ? 58 : edi

   ; rdi = 58 or the first byte,
   ; unlike in the other version where it had 8 bytes of string data here
   mov eax, 0x2000001
   syscall

(Эта версия выглядит намного короче, но большинство дополнительных строк были пробелами, комментариями и метками. Оптимизация до cmp - сразу делает эти 4 инструкции вместо 5 перед mov eax / syscall, но кромечто они равны.)

0 голосов
/ 24 января 2019

Я объясню эти изменения бок о бок (надеюсь, за этим легче будет следовать):

global start

section .data
msg: db "Hello, World!", 10, 0

section .text
start:
   mov rdx, msg
   mov al, [rdx] ; moves one byte from msg, H to al, the 8-bit lower part of ax
   mov ah, 'H'   ; move constant 'H' to the 8-bit upper part of ax
   cmp al, ah    ; compares H with H
   je equal      ; yes, they are equal, so go to address at equal

   mov rax, 0x2000001
   mov rdi, [rdx]
   syscall

equal:           ; here we are
   mov rax, 0x2000001
   mov rdi, 58
   syscall

Если вы не понимаете, как использовать / упоминать al, ah,ax, см. Регистры общего назначения .

...