assembly - 如何将字符串的第一个字符与 x86-64 程序集中的另一个字符进行比较?
问题描述
我有一个初始化的字符串“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
,这是H
. 这让我尝试传递72
而rsi
不是H
,但这也导致程序终止而不跳转到equal
标签。
如何正确比较“Hello, World!”中的第一个字符!与传递给寄存器的字符?
解决方案
您和@Rafael 的回答使您的代码过于复杂。
您通常永远不想使用mov rdi, msg
绝对地址的 64 位立即数。(见Mach-O 64-bit format does not support 32-bit absolute address. NASM Accessing Array)
使用default rel
和使用cmp byte [msg], 'H'
。或者,如果您想要 RDI 中的指针,以便可以在循环中递增它,请使用lea rdi, [rel msg]
.
您的分支之间唯一不同的是 RDI 值。您不需要复制 RAX 设置或syscall
,只需在 RDI 中获取正确的值,然后让分支相互重新连接。(或者无分支地做。)
@Rafael 的答案由于某种原因仍在从字符串中加载 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 的旧值具有错误的依赖性,要么如果您稍后读取完整的寄存器,它可能会导致部分寄存器合并停止。@Rafael 的答案没有这样做,但这mov ah, 'H'
取决于某些 CPU 上加载到 AL 中的情况。请参阅为什么 GCC 不使用部分寄存器?以及Haswell/Skylake 上的部分寄存器的性能如何?编写 AL 似乎对 RAX 有错误的依赖,而 AH 不一致-mov ah, 'H'
对 Haswell/Skylake 上 AH 的旧值有错误的依赖,即使 AH 与 RAX 分开重命名。但是 AL 不是,所以是的,这很可能对负载有错误的依赖性,阻止它并行运行并延迟cmp
一个周期。
无论如何,这里的 TL:DR 是如果你不需要的话,你不应该乱写 AH/BH/CH/DH。阅读它们通常是可以的,但可能会有更糟糕的延迟。请注意,这cmp dil, ah
是不可编码的,因为 DIL 只能通过 REX 前缀访问,而 AH 只能在没有前缀的情况下访问。
我选择了 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 位整数的正常/推荐方法。 合并到旧寄存器值的低字节通常对性能更差,这就是为什么 x86- 32 位寄存器上的 64 条指令将整个 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
-immediate 使这 4 条指令而不是mov eax
/之前的 5 条指令syscall
,但除此之外它们是相等的。)
推荐阅读
- aframe - a-node.js TypeError:无法读取未定义的属性“构造函数”
- android - 如何更新项目的 ID?
- android - 元素之间的约束进入RelativeLayout
- python - sqlalchemy 未对架构执行评论
- algorithm - 为什么说实用的拜占庭容错算法是异步的?
- python - 通过线性插值组合两个不同的时间戳数据帧
- f# - 如何根据 Power Query 中的 Power BI 参数中的逗号分隔值列表删除行?
- r - 如何为 R 包开发解决“在调用‘list’时不允许缺少参数”?
- wpf - SignalR WPF 服务器 - 如何触发播放器?
- c# - 在 for-each 签名中使用方法调用与将方法分配给变量并将变量用作循环的对象