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?
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.