Yasm 和 MSVC 2013 链接器:Win64 上的 RIP

Yasm and MSVC 2013 linker: RIP on Win64

出于学习原因,我正在阅读 "Introduction to 64 Bit Intel Assembly Language Programming for Linux" 并使用 Yasm 和 MS Visual Studio 2013 将代码移植到 Windows。在第 7 章,有一个 switch 的例子:

        global  _tmain

segment .data
switch: dq      _tmain.case0
        dq      _tmain.case1
        dq      _tmain.case2

i:      dq      1

segment .text

_tmain:

        mov     rax, [qword i]
        jmp     [switch+rax*8]

.case0:
        mov     rbx, 100
        jmp     .end

.case1:
        mov     rbx, 101
        jmp     .end

.case2:
        mov     rbx, 102

.end:
        xor     rax, rax
        ret

我从链接器得到:

Microsoft (R) Incremental Linker Version 12.00.30501.0
Copyright (C) Microsoft Corporation.  All rights reserved.

switch2.obj : error LNK2017: 'ADDR32' relocation to 'switch' invalid without /LARGEADDRESSAWARE:NO
LINK : fatal error LNK1165: link failed because of fixup errors

但是,我试图弄清楚发生了什么,我知道这是 x64 架构上的一些寻址问题。所以我将代码更改为:

        mov     rax, [qword i]
        lea     rbx, [rel switch]
        imul    rax, 0x8
        add     rbx, rax
        jmp     [rbx]

代码有效。但是,我有一个问题:这段代码应该使用 gcc 或 ld 作为链接器在 Linux 上工作。为什么我需要修改代码?

由于架构限制,只能在指令中编码 32 位有符号位移。因此,如果 switch 的地址不在以零为中心的地址 space 的 4GiB 范围内,您的代码将无法运行。

它也不会在 linux 上运行,但工具链并没有帮助您。 Microsoft 链接器正在努力提供帮助,并确保您是有意这样做的。

请注意,同样的 32 位限制也适用于 rip 相对寻址,只是原点是当前指令 1,而不是绝对零。因此,只要符号之间的距离不远,无论加载到何处,代码都可以正常工作。

PS:代码的更简单重写可能是:

    mov     rax, [qword i]
    lea     rbx, [rel switch]
    jmp     [rbx + rax * 8]

1 从技术上讲,下面的指令。