Search code examples
assemblyvisual-studio-2013win64addressingyasm

Yasm and MSVC 2013 linker: RIP on Win64


I am reading "Introduction to 64 Bit Intel Assembly Language Programming for Linux" and porting the code to Windows using Yasm and MS Visual Studio 2013 for learning reasons. At chapter 7, there is an example of 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

And I got from linker:

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

However, I tried to figured out what's going on and I understand it's some addressing problem on x64 architecture. So I changed my code to:

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

And the code worked. But, I have a question: this code is supposed to work on Linux using gcc or ld as linker. Why did i need to modify the code?


Solution

  • Due to architectural limitations, only 32 bit signed displacements can be encoded in the instructions. As such, if the address of switch is not within the 4GiB of the address space centered around zero, your code will not work.

    It will not work on linux either, but the toolchain isn't holding your hand there. The microsoft linker is trying to be helpful and make sure you are doing it on purpose.

    Note that the same 32 bit limit applies to rip relative addressing too, it's just that the origin is the current instruction1, not absolute zero. As such, the code will work no matter where it is loaded as long as the symbols are not far away from each other.

    PS: A somewhat simpler rewrite of the code could have been:

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

    1 Technically, the following instruction.