Nasm X86_64 Assembly in 32-Bit Mode: Why Does This Instruction Produce Rip-Relative Addressing Code

NASM x86_64 assembly in 32-bit mode: Why does this instruction produce RIP-Relative Addressing code?

The answer is it isn't. x86-64 doesn't have RIP-relative addressing in 32-bit emulation mode (this should be obvious because RIP doesn't exist in 32-bit). What's happening is that nasm is compiling you some lovely 32-bit opcodes that you're trying to run as 64-bit. GDB is disassembling your 32-bit opcodes as 64-bit, and telling you that in 64-bit, those bytes mean a RIP-relative mov. 64-bit and 32-bit opcodes on the x86-64 overlap a lot to make use of common decoding logic in the silicon, and you're getting confused because the code that GDB is disassembling looks similar to the 32-bit code you wrote, but in reality you're just throwing garbage bytes at the processor.

This isn't anything to do with nasm. You're using the wrong architecture for the process you're in. Either use 32-bit nasm in a 32-bit process or compile your assembly code for [BITS 64].

Why is a RIP-relative LEA instruction producing a PIC-incompatible R_X86_64_32S relocation?

There is no such instruction as lea rbx, [rel values + rdi * 8], or in other words lea rbx, [values + rip + rdi * 8]. The rip-relative addressing mode cannot be combined with an index register (with or without scaling). See Referencing the contents of a memory location. (x86 addressing modes)

Unfortunately, it looks like nasm handles this by just ignoring the rel and assembling lea rbx, [values + rdi * 8], which would require the problematic relocation. The address of values would have to go in the 32-bit displacement field of the instruction's memory operand, but that is impossible since values need not be located in the lowest or highest 2 GB of memory; hence the confusing error message.

But the correct solution is that you just have to write more instructions. You can get the desired effect in two instructions with

lea rbx, [rel values]
lea rbx, [rbx + rdi * 8]

Or if you want to actually do the load:

lea rbx, [rel values]
mov rcx, [rbx + rdi * 8]

(The choices of registers are arbitrary.)

Why does this MOVSS instruction use RIP-relative addressing?

RIP is the instruction pointer register, which means that it contains the address of the instruction immediately following the current instruction.

For example, consider the following code:

mov  rax, [rip]
nop

In the first line of code there, RIP points to the next instruction, so it points at the NOP. Thus, this code loads the address of the NOP instruction into the RAX register.

As such, it is not the case that RIP is simply a constant. Your understanding that RIP in this process "will always be 842" is not correct. The value of RIP will change, depending on where the code has been loaded into memory. 842 is just the line number, pulled from your debugging symbols; once code is compiled into a binary, it doesn't have line numbers anymore. :-)

In your disassembly, the constant is the offset (0x21A). That's the offset from the current value in RIP. Another way of writing this is: %rip + 0x21A.

RIP-relative addressing is a new form of effective addressing introduced with 64-bit long mode. The point is that it makes it easier to write position-independent code because you can make any memory reference RIP-relative. In fact, RIP-relative addressing is the default addressing mode in 64-bit applications. Virtually all instructions that address memory in 64-bit mode are RIP-relative. I'll quote from Ken Johnson (aka Skywing)'s blog because I couldn't say it any better myself:

One of the larger (but often overlooked) changes to x64 with respect to x86 is that most instructions that previously only referenced data via absolute addressing can now reference data via RIP-relative addressing.

RIP-relative addressing is a mode where an address reference is provided as a (signed) 32-bit displacement from the current instruction pointer. While this was typically only used on x86 for control transfer instructions (call, jmp, and soforth), x64 expands the use of instruction pointer relative addressing to cover a much larger set of instructions.

What’s the advantage of using RIP-relative addressing? Well, the main benefit is that it becomes much easier to generate position independent code, or code that does not depend on where it is loaded in memory. This is especially useful in today’s world of (relatively) self-contained modules (such as DLLs or EXEs) that contain both data (global variables) and the code that goes along with it. If one used flat addressing on x86, references to global variables typically required hardcoding the absolute address of the global in question, assuming the module loads at its preferred base address. If the module then could not be loaded at the preferred base address at runtime, the loader had to perform a set of base relocations that essentially rewrite all instructions that had an absolute address operand component to refer to take into account the new address of the module.

[ . . . ]

An instruction that uses RIP relative addressing, however, typically does not require any base relocations (otherwise known as “fixups”) at load time if the module containing it is relocated, however. This is because as long as portions of the module are not internally re-arranged in memory (something not supported by the PE format), any addresses reference that is both relative to the current instruction pointer and refers to a location within the confines of the current image will continue to refer to the correct location, no matter where the image is placed at load time.

As a result, many x64 images have a greatly reduced number of fixups, due to the fact that most operations can be performed in an RIP-relative fashion.

He's speaking in the context of Windows, but something conceptually similar applies on other operating systems as well.

The code you have is loading a constant value, stored somewhere in the binary image, into the XMM0 register, and it's doing so using RIP-relative addressing because of its many advantages.

Why use RIP-relative addressing in NASM?

What is so special about lea that mov can't do?

mov reg,imm loads an immediate constant into its destination operand. Immediate constant is encoded directly in the opcode, e.g. mov eax,someVar would be encoded as B8 EF CD AB 00 if address of someVar is 0x00ABCDEF. I.e. to encode such an instruction with imm being address of msg you need to know exact address of msg. In position-independent code you don't know it a priori.

mov reg,[expression] loads the value located at address described by expression. The complex encoding scheme of x86 instructions allows to have quite complex expression: in general it's reg1+reg2*s+displ, where s can be 0,1,2,4, reg1 and reg2 can be general-purpose registers or zero, and displ is immediate displacement. In 64-bit mode expression can have one more form: RIP+displ, i.e. the address is calculated relative to the next instruction.

lea reg,[expression] uses all this complex way of calculating addresses to load the address itself into reg (unlike mov, which dereferences the address calculated). Thus the information, unavailable at compilation time, namely absolute address which would be in RIP, can be encoded in the instruction without knowing its value. The nasm expression lea rsi,[rel msg] gets translated into something like

    lea rsi,[rip+(msg-nextInsn)]
nextInsn:

which uses the relative address msg-nextInsn instead of absolute address of msg, thus allowing the assembler to not know the actual address but still encode the instruction.

NASM Intel 64-bit mode: why does 32-bit constant 0xffffffff cause warning: signed dword value exceeds bounds

0x00000000FFFFFFFF can't be encoded as a 32-bit sign-extended immediate, and x86 immediates are always sign-extended when they're narrower than the operand-size.


Your theory is somewhat correct, the instruction adds the value specified to the register. There is no way to encode an instruction that adds 0xffffffff to the register, because the only available encodings for adding an immediate to a 64-bit register are add r/m64, sign_extended_imm32 or add r/m64, sign_extended_imm8. Only one special encoding of mov (with a register destination) can use a 64-bit immediate.

The obvious encoding (stuffing 0xffffffff into an imm32) would in fact add 0xffffffffffffffff to rbx which is not what you requested and would be the same as add rbx, -1

There is no way to encode a value into the 32 bit field that results in 0x00000000ffffffff being added to rbx. Asm source uses values, not bit-patterns for immediates.

You need to do something like this :-

mov rcx, 0xffffffff     ; RCX = 0xFFFFFFFF = 0x00000000FFFFFFFF
add rbx, rcx

mov ecx, 0xffffffff would also set RCX the same way; NASM will optimize this for you to an efficient 5-byte instruction (with implicit zero-extension to 64-bit when writing a 32-bit register) instead of needing a 10-byte instruction with a 64-bit immediate; see another Q&A for more about the different forms of MOV in machine code.

Force RIP-relative access to fixed offset in NASM

The syntax you're looking for is [rel $+0x1000] for an offset from the current location, or [rel instrument+0x1000] for an offset from the label. In the example in your question, those happen to be the same since the mov is the first thing after the label, but if there were anything in between, they'd be different.



Related Topics



Leave a reply



Submit