The Netwide Assembler: NASM

Next Chapter | Previous Chapter | Contents | Index

Chapter 10: Writing 64-bit Code (Unix, Win64)

This chapter attempts to cover some of the common issues involved when writing 64-bit code, to run under Win64 or Unix. It covers how to write assembly code to interface with 64-bit C routines, and how to write position-independent code for shared libraries.

All 64-bit code uses a flat memory model, since segmentation is not available in 64-bit mode. The one exception is the FS and GS registers, which still add their bases.

Position independence in 64-bit mode is significantly simpler, since the processor supports RIP-relative addressing directly; see the REL keyword (section 3.3). On most 64-bit platforms, it is probably desirable to make that the default, using the directive DEFAULT REL (section 5.2).

64-bit programming is relatively similar to 32-bit programming, but of course pointers are 64 bits long; additionally, all existing platforms pass arguments in registers rather than on the stack. Furthermore, 64-bit platforms use SSE2 by default for floating point. Please see the ABI documentation for your platform.

64-bit platforms differ in the sizes of the fundamental datatypes, not just from 32-bit platforms but from each other. If a specific size data type is desired, it is probably best to use the types defined in the Standard C header <inttypes.h>.

In 64-bit mode, the default instruction size is still 32 bits. When loading a value into a 32-bit register (but not an 8- or 16-bit register), the upper 32 bits of the corresponding 64-bit register are set to zero.

10.1 Register names in 64-bit mode

NASM uses the following names for general-purpose registers in 64-bit mode, for 8-, 16-, 32- and 64-bit references, respecitively:

     AL/AH, CL/CH, DL/DH, BL/BH, SPL, BPL, SIL, DIL, R8B-R15B 
     AX, CX, DX, BX, SP, BP, SI, DI, R8W-R15W 
     EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI, R8D-R15D 
     RAX, RCX, RDX, RBX, RSP, RBP, RSI, RDI, R8-R15

This is consistent with the AMD documentation and most other assemblers. The Intel documentation, however, uses the names R8L-R15L for 8-bit references to the higher registers. It is possible to use those names by definiting them as macros; similarly, if one wants to use numeric names for the low 8 registers, define them as macros. See the file altreg.inc in the misc directory of the NASM source distribution.

10.2 Immediates and displacements in 64-bit mode

In 64-bit mode, immediates and displacements are generally only 32 bits wide. NASM will therefore truncate most displacements and immediates to 32 bits.

The only instruction which takes a full 64-bit immediate is:

     MOV reg64,imm64

NASM will produce this instruction whenever the programmer uses MOV with an immediate into a 64-bit register. If this is not desirable, simply specify the equivalent 32-bit register, which will be automatically zero-extended by the processor, or specify the immediate as DWORD:

     mov rax,foo             ; 64-bit immediate 
     mov rax,qword foo       ; (identical) 
     mov eax,foo             ; 32-bit immediate, zero-extended 
     mov rax,dword foo       ; 32-bit immediate, sign-extended

The length of these instructions are 10, 5 and 7 bytes, respectively.

The only instructions which take a full 64-bit displacement is loading or storing, using MOV, AL, AX, EAX or RAX (but no other registers) to an absolute 64-bit address. Since this is a relatively rarely used instruction (64-bit code generally uses relative addressing), the programmer has to explicitly declare the displacement size as QWORD:

     default abs 

     mov eax,[foo]           ; 32-bit absolute disp, sign-extended 
     mov eax,[a32 foo]       ; 32-bit absolute disp, zero-extended 
     mov eax,[qword foo]     ; 64-bit absolute disp 

     default rel 

     mov eax,[foo]           ; 32-bit relative disp 
     mov eax,[a32 foo]       ; d:o, address truncated to 32 bits(!) 
     mov eax,[qword foo]     ; error 
     mov eax,[abs qword foo] ; 64-bit absolute disp

A sign-extended absolute displacement can access from -2 GB to +2 GB; a zero-extended absolute displacement can access from 0 to 4 GB.

10.3 Interfacing to 64-bit C Programs (Unix)

On Unix, the 64-bit ABI is defined by the document:

http://www.x86-64.org/documentation/abi.pdf

Although written for AT&T-syntax assembly, the concepts apply equally well for NASM-style assembly. What follows is a simplified summary.

The first six integer arguments (from the left) are passed in RDI, RSI, RDX, RCX, R8, and R9, in that order. Additional integer arguments are passed on the stack. These registers, plus RAX, R10 and R11 are destroyed by function calls, and thus are available for use by the function without saving.

Integer return values are passed in RAX and RDX, in that order.

Floating point is done using SSE registers, except for long double. Floating-point arguments are passed in XMM0 to XMM7; return is XMM0 and XMM1. long double are passed on the stack, and returned in ST(0) and ST(1).

All SSE and x87 registers are destroyed by function calls.

On 64-bit Unix, long is 64 bits.

Integer and SSE register arguments are counted separately, so for the case of

     void foo(long a, double b, int c)

a is passed in RDI, b in XMM0, and c in ESI.

10.4 Interfacing to 64-bit C Programs (Win64)

The Win64 ABI is described at:

http://msdn2.microsoft.com/en-gb/library/ms794533.aspx

What follows is a simplified summary.

The first four integer arguments are passed in RCX, RDX, R8 and R9, in that order. Additional integer arguments are passed on the stack. These registers, plus RAX, R10 and R11 are destroyed by function calls, and thus are available for use by the function without saving.

Integer return values are passed in RAX only.

Floating point is done using SSE registers, except for long double. Floating-point arguments are passed in XMM0 to XMM3; return is XMM0 only.

On Win64, long is 32 bits; long long or _int64 is 64 bits.

Integer and SSE register arguments are counted together, so for the case of

     void foo(long long a, double b, int c)

a is passed in RCX, b in XMM1, and c in R8D.

Next Chapter | Previous Chapter | Contents | Index