如何在x86-64程序集中比较字符串的第一个字符和另一个字符?

时间:2019-01-24 08:32:15

标签: assembly nasm x86-64

我有一个初始化的字符串“ Hello,World!”。我想从中提取第一个字符(即“ H”)并将其比较一个在运行时传递到寄存器中的字符。

我尝试比较“ Hello,World!”的第一个字符。通过以下代码用“ H”表示:

"box"{
     "quotations": [
     {
          "lifeAssured": [
              "Hieu Vo"
           ],
     }
     ]
}

但是,此代码终止而不跳转到while($row = $result->fetch_assoc()) { $total = empty($row['total']) ? '-' : preg_replace('/[^0-9]+/','',$row['total']); echo "<tr>"; echo "<td>" . $total . "</td>"; echo "</tr>"; } 标签。此外,程序的退出状态为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的ASCII码。这使我尝试将72传递给H而不是72,但这也导致程序终止而没有跳转到rsi标签。

如何正确比较“世界你好!”中的第一个字符带有传递到寄存器的字符?

2 个答案:

答案 0 :(得分:3)

您和@Rafael的答案使您的代码过于复杂化。

通常,您绝对不想在绝对地址的64位立即数上使用mov rdi, msg。 (请参见Mach-O 64-bit format does not support 32-bit absolute addresses. NASM Accessing Array

使用default rel并使用cmp byte [msg], 'H'。或者,如果您想要RDI中的指针以便可以在循环中递增,请使用lea rdi, [rel msg]

您的分支之间唯一不同的是RDI值。您无需复制RAX设置或syscall,只需在RDI中获得正确的值,然后使分支彼此重新连接即可。 (或无分支地进行。)

@Rafael的答案出于某种原因仍在从字符串中加载8个字节,例如问题中的两个加载。大概是sys_exit,它忽略了高字节,只设置了低字节的进程退出状态,但是为了好玩,让我们假装我们实际上希望所有8个字节都为syscall加载,而只比较低字节。

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的旧值有错误的依赖性,或者如果您稍后读取完整的寄存器,则可能导致部分寄存器合并停顿。 @Rafael的答案不是那样,但是mov ah, 'H'取决于某些CPU上AL的负载。请参见Why doesn't GCC use partial registers?How exactly do partial registers on Haswell/Skylake perform? Writing AL seems to have a false dependency on RAX, and AH is inconsistent-mov ah, 'H'对Haswell / Skylake上AH的旧值有错误的依赖性,即使AH与RAX分开重命名也是如此。但是AL不是,所以是的,这很可能对负载有错误的依赖性,从而使其无法并行运行,并使cmp延迟一个周期。

无论如何,这里的TL:DR是不需要的话,不要乱写AH / BH / CH / DH。读取它们通常没问题,但可能会导致更糟的延迟。请注意,cmp dil, ah是不可编码的,因为DIL仅可使用REX前缀访问,而AH仅可在没有REX前缀的情况下访问。

我选择RCX而不是RSI,因为CL不需要REX前缀,但是由于我们需要查看RDI的低字节(dil),因此无论如何在cmp上都需要一个REX前缀。我本可以使用mov cl, 'H'来保存代码大小,因为错误地依赖RCX的旧值可能没有问题。


顺便说一句,cmp dil, 'H'cmp dil, cl一样好。

或者,如果我们将零扩展的字节加载到完整的RDI中,则可以使用cmp edi, 'H'而不是其低8版本。 (零扩展加载是在现代x86-64上处理字节和16位整数的正常/建议方法。合并到旧寄存器值的低字节通常会降低性能,这会降低性能。是Why do x86-64 instructions on 32-bit registers zero the upper part of the full 64-bit register?的原因。)

我们可以选择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,但两者相等。

答案 1 :(得分:1)

我将并排解释更改(希望更容易理解):

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

如果您不了解alahax的使用/提及,请参阅General-Purpose Registers