`jz` 到 RIP 相对内存位置会产生操作数不匹配 (GCC)

`jz` to RIP-relative memory location produces operand mismatch (GCC)

.macro alloc_linked_list_item_rdi_rax
        movq , %rdi
        call malloc@PLT
        testq %rax, %rax
        leaq 0x1a(%rip), %r10
        jz *%r10  # <=
        movq [=10=]xA, 0x0(%rax)  # void *data
        movq [=10=]x0, 0x8(%rax)  # struct linked_list_item *prev
        movq [=10=]x0, 0x10(%rax) # struct linked_list_item *next
.endm

当我尝试 assemble 以下宏时(在 GNU/Linux Debian x86-64 上使用 GCC) assembler 生成 ll.s:86: Error: operand type mismatch for 'jz' 其中 ll.s:86指的是在这个宏里面,在这个特定的 jz.

对于这个宏,我尝试使用本地标签来管理控制流,但是它引发了错误,我不能有重复的标签名称(因为我在程序中总共使用了两次宏),所以我转向对 RIP 的配置跳转(使用 objdump 计算,然后计算指令的长度)。

如果我将 jz 替换为无条件的 jmp,则给定的程序集有效,至于这个,我很困惑为什么我编写的代码不起作用。

任何人都可以解释一下吗?

x86 指令集只有带有立即操作数的条件跳转(它描述了相对于程序计数器的偏移量)。其他操作数需要无条件跳转指令。解决此问题的一个简单方法是反转跳转的含义并跳过无条件跳转指令,如下所示:

        jnz .Lalloc_linked_list_item_rdi_rax.\@
        jmp *%r10
.Lalloc_linked_list_item_rdi_rax.\@:

(搞笑的标签名是为了避免与其他标签冲突。)

如果我没记错的话,一些旧的汇编程序会自动执行此操作,但是当静态分支预测到达 CPUs 时,这变得不那么有用而且实际上非常混乱,因为它掩盖了分支是如何被 CPU。 (但大多数当前的 x86 CPUs 似乎不再那么依赖静态分支预测,所以我们回到了起点。)